Compare commits
393 Commits
CS2-Simple
...
build-1.7.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdada2df1e | ||
|
|
310a43fcd9 | ||
|
|
58243e813a | ||
|
|
d53446e0fe | ||
|
|
2404c1bc03 | ||
|
|
665962565e | ||
|
|
c2e8b4a898 | ||
|
|
9723a4faee | ||
|
|
4865b76262 | ||
|
|
0dded66e5d | ||
|
|
038641dbdf | ||
|
|
a03964c08a | ||
|
|
b0d8696756 | ||
|
|
21a5de6b3d | ||
|
|
718536eaef | ||
|
|
099e91b43b | ||
|
|
206c18db66 | ||
|
|
78318102fe | ||
|
|
2edacc2b3f | ||
|
|
e1e66441f2 | ||
|
|
cc54b9e879 | ||
|
|
640e618f3b | ||
|
|
23d174c4a5 | ||
|
|
b7371adf26 | ||
|
|
9154748ce6 | ||
|
|
da9830ee05 | ||
|
|
b41ac992c0 | ||
|
|
af0bda8f3a | ||
|
|
e2529cd646 | ||
|
|
9d2cd34845 | ||
|
|
5701455de0 | ||
|
|
b97426313b | ||
|
|
3ab63c05db | ||
|
|
f654d6b085 | ||
|
|
676a18d9b4 | ||
|
|
d75a092047 | ||
|
|
d34ca64970 | ||
|
|
f69f1277f8 | ||
|
|
b6c876d709 | ||
|
|
888d6b0152 | ||
|
|
5d62c743dd | ||
|
|
62b1987fde | ||
|
|
708ae6cb90 | ||
|
|
2d77e86d59 | ||
|
|
babcbc2119 | ||
|
|
64e5f1156e | ||
|
|
8cc0398f6b | ||
|
|
ab14956ae5 | ||
|
|
9163caeb0d | ||
|
|
f2e4b84b29 | ||
|
|
3f1b6b3bf7 | ||
|
|
8af805632a | ||
|
|
8c94a867d3 | ||
|
|
023e1a031b | ||
|
|
1f1c214357 | ||
|
|
2defb2fe14 | ||
|
|
b2ebe136c3 | ||
|
|
5668c0ad7b | ||
|
|
70a62d4b63 | ||
|
|
95818e2742 | ||
|
|
7154843d1d | ||
|
|
c42d2ddeeb | ||
|
|
7a69c5387a | ||
|
|
82b82722a6 | ||
|
|
b38b9a0751 | ||
|
|
5a9367ae89 | ||
|
|
d30ac80a36 | ||
|
|
9820d74095 | ||
|
|
d0207f3d0b | ||
|
|
2bee514e32 | ||
|
|
9d87fd8701 | ||
|
|
6847da2af0 | ||
|
|
757a39f90e | ||
|
|
bd817d6652 | ||
|
|
4206ad18b2 | ||
|
|
774d812723 | ||
|
|
cf5c1d8440 | ||
|
|
efa2a392d6 | ||
|
|
32520c7e84 | ||
|
|
702a0315b8 | ||
|
|
b1ab6e0e0d | ||
|
|
e272449f3c | ||
|
|
05893a90d3 | ||
|
|
a3b8d8cfa7 | ||
|
|
1cf541bb51 | ||
|
|
3fb254bd0a | ||
|
|
a26df7fedd | ||
|
|
446dc9f4f5 | ||
|
|
3a8e0dd1fc | ||
|
|
b90ed05d1b | ||
|
|
6df98fb164 | ||
|
|
100482cb17 | ||
|
|
8f2f95ca23 | ||
|
|
79270acafe | ||
|
|
f95581e7c0 | ||
|
|
af46f0bca1 | ||
|
|
f56e3f6fe9 | ||
|
|
4ef07c8bf7 | ||
|
|
0674b7e492 | ||
|
|
c4cb308147 | ||
|
|
bb0a236f28 | ||
|
|
3bc51330af | ||
|
|
5fdefc9614 | ||
|
|
bcbcb83a35 | ||
|
|
6cf6b1c919 | ||
|
|
3b98f19a7c | ||
|
|
83fdf1dd56 | ||
|
|
6fc8b015ba | ||
|
|
6b5e36087e | ||
|
|
a395a7d9c8 | ||
|
|
d7e46525a5 | ||
|
|
cdd771511b | ||
|
|
8e4724fb3e | ||
|
|
985b1ae61f | ||
|
|
00facafdcb | ||
|
|
962529e445 | ||
|
|
873fed17c9 | ||
|
|
fc2958c84f | ||
|
|
4244104d6e | ||
|
|
d5a6ceacb6 | ||
|
|
14cc9d3b00 | ||
|
|
9fb256d39f | ||
|
|
c25d3c4bda | ||
|
|
ebf9e06bcd | ||
|
|
c72c7231f7 | ||
|
|
9c870b8fc3 | ||
|
|
cf7a87d959 | ||
|
|
9759b34505 | ||
|
|
5a90171842 | ||
|
|
19f8b68c1c | ||
|
|
342d4f717f | ||
|
|
731bc229d3 | ||
|
|
bb8cec7cf8 | ||
|
|
ad4c721a27 | ||
|
|
7efa2cdad3 | ||
|
|
c321502937 | ||
|
|
13ab223c86 | ||
|
|
c73584edae | ||
|
|
aefa6c6355 | ||
|
|
806b5038ca | ||
|
|
b45e112534 | ||
|
|
e1650df72e | ||
|
|
8caa8669ea | ||
|
|
516d9d9014 | ||
|
|
c71391bca4 | ||
|
|
2c66b899d0 | ||
|
|
1cfff8684f | ||
|
|
0bbf1948bf | ||
|
|
b4103bfc25 | ||
|
|
a0b2e59357 | ||
|
|
fb251aadef | ||
|
|
00343b6b66 | ||
|
|
270b36fa26 | ||
|
|
7a8fd066f7 | ||
|
|
90556e66b2 | ||
|
|
143f0b7e1b | ||
|
|
d8a1a1e471 | ||
|
|
3abf246f9b | ||
|
|
d1fdaa4ecf | ||
|
|
89d27e1bd3 | ||
|
|
bd82a981f5 | ||
|
|
052cea5ce3 | ||
|
|
ba2bf90340 | ||
|
|
8f80e13100 | ||
|
|
a377871951 | ||
|
|
8df1e70d5a | ||
|
|
d8e30e02e9 | ||
|
|
b2c56d3fa1 | ||
|
|
5609748d19 | ||
|
|
9d513a92a1 | ||
|
|
3a977f688c | ||
|
|
143dc138f0 | ||
|
|
a8904f2d8a | ||
|
|
39404e52ac | ||
|
|
0cca38b10b | ||
|
|
aac5caa565 | ||
|
|
90c0564f56 | ||
|
|
0f4a2835d2 | ||
|
|
c6126ab2ec | ||
|
|
60c76562f9 | ||
|
|
ac940259f7 | ||
|
|
cce265c6b7 | ||
|
|
525905194c | ||
|
|
2ab2f9c4dc | ||
|
|
da6fb2fc22 | ||
|
|
7d5166cf4b | ||
|
|
a8c4c1f9fa | ||
|
|
3c42acf15e | ||
|
|
f61a5ff6a8 | ||
|
|
229b8d73a3 | ||
|
|
650c115a88 | ||
|
|
4d3cefc495 | ||
|
|
5d58465c74 | ||
|
|
5bf966f9cd | ||
|
|
619bdfbb14 | ||
|
|
f4f669d498 | ||
|
|
8e1a1b2ecf | ||
|
|
64803ebff2 | ||
|
|
285429dc66 | ||
|
|
5b36a5fc62 | ||
|
|
4e898a6509 | ||
|
|
af66802b37 | ||
|
|
6c44281ca5 | ||
|
|
5edddbb70f | ||
|
|
a8a35544c0 | ||
|
|
74852f0651 | ||
|
|
1882b14657 | ||
|
|
c3dd3bed10 | ||
|
|
af73447b9e | ||
|
|
7386a2aae2 | ||
|
|
effe97a210 | ||
|
|
3b6b7fbcab | ||
|
|
9a7a143478 | ||
|
|
fe834d8abc | ||
|
|
eb9519c156 | ||
|
|
8750a66eef | ||
|
|
3317182b9a | ||
|
|
fa5296fec7 | ||
|
|
0a2d19accd | ||
|
|
c2f7b3be08 | ||
|
|
b31bb871a6 | ||
|
|
00819beec5 | ||
|
|
ae8d9db5e4 | ||
|
|
a3ebcccb6f | ||
|
|
dd3ccf4069 | ||
|
|
3413200d8e | ||
|
|
6dccddf929 | ||
|
|
751c97b4cf | ||
|
|
b4c259595e | ||
|
|
d8e8073dd6 | ||
|
|
377049c020 | ||
|
|
74c773aaea | ||
|
|
e2df860b6f | ||
|
|
b1e497ea3f | ||
|
|
953234078c | ||
|
|
bda704e843 | ||
|
|
e4e1023684 | ||
|
|
5d02343268 | ||
|
|
778c93b170 | ||
|
|
9cecc19060 | ||
|
|
f0b5b820e2 | ||
|
|
6a182fff9d | ||
|
|
cd8ee681b2 | ||
|
|
42849c231f | ||
|
|
76914be555 | ||
|
|
aadcaa0e64 | ||
|
|
0b2a520a07 | ||
|
|
79bbe0f4c5 | ||
|
|
01ceb104c5 | ||
|
|
e401fe7c8b | ||
|
|
fbed647699 | ||
|
|
aa95815fbd | ||
|
|
3793385ce4 | ||
|
|
c8c0a3f0ba | ||
|
|
5991f85919 | ||
|
|
a85e5c69ee | ||
|
|
40ad4b995c | ||
|
|
d3062b5f7e | ||
|
|
131030a2cd | ||
|
|
a9f3196441 | ||
|
|
bdb90b0cc6 | ||
|
|
93faad27c1 | ||
|
|
7251516bb4 | ||
|
|
175d6df250 | ||
|
|
7e5dc7b612 | ||
|
|
0572ad7d32 | ||
|
|
86e837931c | ||
|
|
b2f1afd7e7 | ||
|
|
705bacccae | ||
|
|
58086c4009 | ||
|
|
f15061793a | ||
|
|
5035f88da0 | ||
|
|
4e597d73a5 | ||
|
|
13d36dca12 | ||
|
|
9acab82f86 | ||
|
|
66f0ebe08c | ||
|
|
0cd1653185 | ||
|
|
fb827f4f0d | ||
|
|
6848847f21 | ||
|
|
a80de2d7dc | ||
|
|
a266f776a8 | ||
|
|
60ebb698a3 | ||
|
|
5fbd21aec2 | ||
|
|
232f487d01 | ||
|
|
f622701f5e | ||
|
|
65b33c17ea | ||
|
|
4fd268b235 | ||
|
|
bad4289c5c | ||
|
|
fd97e43490 | ||
|
|
a59cf5a7da | ||
|
|
dd7a2f4c16 | ||
|
|
fbf395d327 | ||
|
|
8ca82804a2 | ||
|
|
4b1cb3573b | ||
|
|
5dc35ddb73 | ||
|
|
31592bf1a5 | ||
|
|
a3471e2091 | ||
|
|
dfd9bd5329 | ||
|
|
e028bef5b0 | ||
|
|
67ad1851bf | ||
|
|
e584316a28 | ||
|
|
450a7804c6 | ||
|
|
855f087a7b | ||
|
|
b5600d7e73 | ||
|
|
6640d00442 | ||
|
|
8aee44f709 | ||
|
|
374f6002eb | ||
|
|
99ce2cdf6a | ||
|
|
38020e6509 | ||
|
|
d751bdadbd | ||
|
|
64c806f3d7 | ||
|
|
8c4cb49d02 | ||
|
|
2d41a62b52 | ||
|
|
48baac0a7b | ||
|
|
8d365b670e | ||
|
|
6a82d72244 | ||
|
|
e602eaa445 | ||
|
|
75f18d3c0a | ||
|
|
40611e9126 | ||
|
|
fa8b7a877f | ||
|
|
71af90031e | ||
|
|
07924a6a74 | ||
|
|
f78cd888de | ||
|
|
d173816492 | ||
|
|
dd5e72e873 | ||
|
|
cfe79109a8 | ||
|
|
18122cdc08 | ||
|
|
ac92f1cb4e | ||
|
|
6d6ff53b02 | ||
|
|
675c7bdcee | ||
|
|
c839e69804 | ||
|
|
3930d2d4bd | ||
|
|
6a5165a513 | ||
|
|
5dc14e3301 | ||
|
|
27582b72af | ||
|
|
f95031a3f5 | ||
|
|
7a73212d2e | ||
|
|
270e3bd858 | ||
|
|
b8075ffa8c | ||
|
|
28f6cf63fe | ||
|
|
e040dbced5 | ||
|
|
7e7fc5c885 | ||
|
|
15ab2d9e65 | ||
|
|
2105d80f52 | ||
|
|
d677551463 | ||
|
|
efd9ab5c22 | ||
|
|
e6fb8bc402 | ||
|
|
9712f04e38 | ||
|
|
719caeafef | ||
|
|
22bb7ebac0 | ||
|
|
b813a422a2 | ||
|
|
36e5cd48a5 | ||
|
|
34342c5134 | ||
|
|
5183661c2a | ||
|
|
6c075cdafe | ||
|
|
a56b5f7b3f | ||
|
|
07b79cb91a | ||
|
|
607c38e992 | ||
|
|
dbdefa8168 | ||
|
|
b8048e1500 | ||
|
|
59975bb49d | ||
|
|
454643431a | ||
|
|
6ac370df74 | ||
|
|
f3a1200492 | ||
|
|
90ca6c1ac2 | ||
|
|
29fe968155 | ||
|
|
fdbf1b1a5c | ||
|
|
dcc85b21c3 | ||
|
|
9c293e6201 | ||
|
|
bd0f21b2ad | ||
|
|
13e170a9c4 | ||
|
|
696b8c3877 | ||
|
|
f19c594568 | ||
|
|
90efff5d24 | ||
|
|
2ea03caeed | ||
|
|
d16b92e694 | ||
|
|
0f32daa4c0 | ||
|
|
86378324a7 | ||
|
|
bce6bd4348 | ||
|
|
5ea71f1679 | ||
|
|
53812148b3 | ||
|
|
d496977c76 | ||
|
|
35e6a52ad8 | ||
|
|
68a446a515 | ||
|
|
0a0c0cfbc4 | ||
|
|
39e73763b7 | ||
|
|
ad621b9cb8 | ||
|
|
31bc91e0a4 | ||
|
|
fa508c965a | ||
|
|
d2d1f41e80 | ||
|
|
ffcd623628 | ||
|
|
e4a6c733ab | ||
|
|
6bcb10c5cf |
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ko_fi: daffyy
|
||||
128
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
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_FUNCOMMANDSMODULE: "Modules/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.csproj"
|
||||
PROJECT_NAME_FUNCOMMANDSMODULE: "CS2-SimpleAdmin_FunCommands"
|
||||
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_FUNCOMMANDSMODULE }}
|
||||
dotnet build ${{ env.PROJECT_PATH_FUNCOMMANDSMODULE }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_FUNCOMMANDSMODULE }}
|
||||
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_FunCommands
|
||||
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_FUNCOMMANDSMODULE }}/* ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin_FunCommands
|
||||
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.
|
||||
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
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
|
||||
CLAUDE.md
|
||||
20
CS2-SimpleAdmin-docs/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
41
CS2-SimpleAdmin-docs/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Website
|
||||
|
||||
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
## Local Development
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
|
||||
## Deployment
|
||||
|
||||
Using SSH:
|
||||
|
||||
```bash
|
||||
USE_SSH=true yarn deploy
|
||||
```
|
||||
|
||||
Not using SSH:
|
||||
|
||||
```bash
|
||||
GIT_USER=<Your GitHub username> yarn deploy
|
||||
```
|
||||
|
||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||
465
CS2-SimpleAdmin-docs/docs/developer/api/commands.md
Normal file
@@ -0,0 +1,465 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Commands API
|
||||
|
||||
Complete reference for command registration and management.
|
||||
|
||||
## Command Registration
|
||||
|
||||
### RegisterCommand
|
||||
|
||||
Register a new command that integrates with CS2-SimpleAdmin.
|
||||
|
||||
```csharp
|
||||
void RegisterCommand(string name, string? description, CommandInfo.CommandCallback callback)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `name` - Command name (e.g., "css_mycommand")
|
||||
- `description` - Command description (optional)
|
||||
- `callback` - Method to call when command is executed
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.RegisterCommand("css_mycommand", "My custom command", OnMyCommand);
|
||||
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Command logic here
|
||||
}
|
||||
```
|
||||
|
||||
**Throws:**
|
||||
- `ArgumentException` - If command name is null or empty
|
||||
- `ArgumentNullException` - If callback is null
|
||||
|
||||
---
|
||||
|
||||
### UnRegisterCommand
|
||||
|
||||
Unregister a previously registered command.
|
||||
|
||||
```csharp
|
||||
void UnRegisterCommand(string commandName)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `commandName` - Name of command to unregister
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.UnRegisterCommand("css_mycommand");
|
||||
```
|
||||
|
||||
**Best Practice:**
|
||||
Always unregister commands in your plugin's `Unload()` method:
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
_api.UnRegisterCommand("css_mycommand");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Target Parsing
|
||||
|
||||
### GetTarget
|
||||
|
||||
Parse player targets from command arguments.
|
||||
|
||||
```csharp
|
||||
TargetResult? GetTarget(CommandInfo command)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `command` - Command info containing arguments
|
||||
|
||||
**Returns:**
|
||||
- `TargetResult` - Contains matched players
|
||||
- `null` - If no targets found or error
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players)
|
||||
{
|
||||
// Do something with player
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Supported Target Syntax:**
|
||||
- `@all` - All players
|
||||
- `@ct` - All Counter-Terrorists
|
||||
- `@t` - All Terrorists
|
||||
- `@spec` - All spectators
|
||||
- `@alive` - All alive players
|
||||
- `@dead` - All dead players
|
||||
- `@bot` - All bots
|
||||
- `@human` - All human players
|
||||
- `@me` - Command caller
|
||||
- `#123` - Player by user ID
|
||||
- `PlayerName` - Player by name (partial match)
|
||||
|
||||
---
|
||||
|
||||
## Command Logging
|
||||
|
||||
### LogCommand (with CommandInfo)
|
||||
|
||||
Log a command execution with full command info.
|
||||
|
||||
```csharp
|
||||
void LogCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `caller` - Player who executed command (null for console)
|
||||
- `command` - Command info object
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Execute command logic
|
||||
|
||||
// Log the command
|
||||
_api!.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### LogCommand (with string)
|
||||
|
||||
Log a command execution with custom command string.
|
||||
|
||||
```csharp
|
||||
void LogCommand(CCSPlayerController? caller, string command)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `caller` - Player who executed command (null for console)
|
||||
- `command` - Command string to log
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
private void DoAction(CCSPlayerController? caller, CCSPlayerController target)
|
||||
{
|
||||
// Perform action
|
||||
|
||||
// Log with custom string
|
||||
_api!.LogCommand(caller, $"css_mycommand {target.PlayerName}");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Example
|
||||
|
||||
### Basic Command
|
||||
|
||||
```csharp
|
||||
private void RegisterCommands()
|
||||
{
|
||||
_api!.RegisterCommand("css_hello", "Say hello to a player", OnHelloCommand);
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnHelloCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid))
|
||||
{
|
||||
player.PrintToChat($"Hello {player.PlayerName}!");
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
_api?.UnRegisterCommand("css_hello");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Command with Permission Check
|
||||
|
||||
```csharp
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/ban")]
|
||||
private void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Get targets
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
// Filter for players caller can target
|
||||
var validPlayers = targets.Players
|
||||
.Where(p => p.IsValid && !p.IsBot && caller!.CanTarget(p))
|
||||
.ToList();
|
||||
|
||||
foreach (var player in validPlayers)
|
||||
{
|
||||
// Issue ban
|
||||
_api.IssuePenalty(player, caller, PenaltyType.Ban, "Banned via command", 1440);
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Command with Arguments
|
||||
|
||||
```csharp
|
||||
[CommandHelper(2, "<#userid or name> <value>")]
|
||||
[RequiresPermissions("@css/slay")]
|
||||
private void OnSetHpCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Parse HP value
|
||||
if (!int.TryParse(command.GetArg(2), out int hp))
|
||||
{
|
||||
caller?.PrintToChat("Invalid HP value!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get targets
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid && p.PawnIsAlive))
|
||||
{
|
||||
player.PlayerPawn?.Value?.SetHealth(hp);
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, $"css_sethp {hp}");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Validate Targets
|
||||
|
||||
```csharp
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return; // No targets found
|
||||
|
||||
// Filter for valid players
|
||||
var validPlayers = targets.Players
|
||||
.Where(p => p.IsValid && !p.IsBot)
|
||||
.ToList();
|
||||
|
||||
if (validPlayers.Count == 0)
|
||||
{
|
||||
caller?.PrintToChat("No valid targets found!");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Check Immunity
|
||||
|
||||
```csharp
|
||||
foreach (var player in targets.Players)
|
||||
{
|
||||
// Check if caller can target this player (immunity check)
|
||||
if (!caller!.CanTarget(player))
|
||||
{
|
||||
caller.PrintToChat($"You cannot target {player.PlayerName}!");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Safe to target player
|
||||
DoAction(player);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Always Log Commands
|
||||
|
||||
```csharp
|
||||
// Log every admin command execution
|
||||
_api.LogCommand(caller, command);
|
||||
```
|
||||
|
||||
### 4. Use CommandHelper Attribute
|
||||
|
||||
```csharp
|
||||
// Specify minimum args and usage
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// CounterStrikeSharp validates args automatically
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Cleanup on Unload
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Unregister ALL commands
|
||||
_api.UnRegisterCommand("css_command1");
|
||||
_api.UnRegisterCommand("css_command2");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Multiple Aliases
|
||||
|
||||
```csharp
|
||||
// Register same command with multiple aliases
|
||||
var aliases = new[] { "css_mycommand", "css_mycmd", "css_mc" };
|
||||
|
||||
foreach (var alias in aliases)
|
||||
{
|
||||
_api.RegisterCommand(alias, "My command", OnMyCommand);
|
||||
}
|
||||
|
||||
// Unregister all
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
foreach (var alias in aliases)
|
||||
{
|
||||
_api?.UnRegisterCommand(alias);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Command from Config
|
||||
|
||||
```csharp
|
||||
// In Config.cs
|
||||
public List<string> MyCommands { get; set; } = ["css_mycommand"];
|
||||
|
||||
// In Plugin
|
||||
private void RegisterCommands()
|
||||
{
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api!.RegisterCommand(cmd, "Description", OnMyCommand);
|
||||
}
|
||||
}
|
||||
|
||||
// Allows users to add aliases or disable by clearing list
|
||||
```
|
||||
|
||||
### Target Filtering
|
||||
|
||||
```csharp
|
||||
// Get only alive players
|
||||
var alivePlayers = targets.Players
|
||||
.Where(p => p.IsValid && p.PawnIsAlive)
|
||||
.ToList();
|
||||
|
||||
// Get only enemy team
|
||||
var enemies = targets.Players
|
||||
.Where(p => p.IsValid && p.Team != caller!.Team)
|
||||
.ToList();
|
||||
|
||||
// Get targetable players
|
||||
var targetable = targets.Players
|
||||
.Where(p => p.IsValid && caller!.CanTarget(p))
|
||||
.ToList();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Command Registration Errors
|
||||
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
_api.RegisterCommand("css_mycommand", "Description", OnMyCommand);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Logger.LogError($"Failed to register command: {ex.Message}");
|
||||
}
|
||||
```
|
||||
|
||||
### Target Parsing Errors
|
||||
|
||||
```csharp
|
||||
var targets = _api!.GetTarget(command);
|
||||
|
||||
if (targets == null)
|
||||
{
|
||||
// Target parsing failed
|
||||
// Error message already sent to caller by SimpleAdmin
|
||||
return;
|
||||
}
|
||||
|
||||
if (targets.Players.Count == 0)
|
||||
{
|
||||
caller?.PrintToChat("No players matched your target!");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Tips
|
||||
|
||||
### Cache Command Lists
|
||||
|
||||
```csharp
|
||||
// Don't create new list every time
|
||||
private readonly List<string> _commandAliases = new() { "css_cmd1", "css_cmd2" };
|
||||
|
||||
private void RegisterCommands()
|
||||
{
|
||||
foreach (var cmd in _commandAliases)
|
||||
{
|
||||
_api!.RegisterCommand(cmd, "Description", OnCommand);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Efficient Target Filtering
|
||||
|
||||
```csharp
|
||||
// ✅ Good - single LINQ query
|
||||
var players = targets.Players
|
||||
.Where(p => p.IsValid && !p.IsBot && p.PawnIsAlive && caller!.CanTarget(p))
|
||||
.ToList();
|
||||
|
||||
// ❌ Bad - multiple iterations
|
||||
var players = targets.Players.Where(p => p.IsValid).ToList();
|
||||
players = players.Where(p => !p.IsBot).ToList();
|
||||
players = players.Where(p => p.PawnIsAlive).ToList();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related APIs
|
||||
|
||||
- **[Menus API](menus)** - Create interactive menus
|
||||
- **[Penalties API](penalties)** - Issue penalties from commands
|
||||
- **[Utilities API](utilities)** - Helper functions for commands
|
||||
642
CS2-SimpleAdmin-docs/docs/developer/api/events.md
Normal file
@@ -0,0 +1,642 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Events API
|
||||
|
||||
Complete reference for CS2-SimpleAdmin event system.
|
||||
|
||||
## Event System Overview
|
||||
|
||||
CS2-SimpleAdmin exposes events that allow modules to react to plugin actions and state changes.
|
||||
|
||||
**All events use C# event delegates and should be subscribed to in `OnAllPluginsLoaded` and unsubscribed in `Unload`.**
|
||||
|
||||
---
|
||||
|
||||
## Plugin Lifecycle Events
|
||||
|
||||
### OnSimpleAdminReady
|
||||
|
||||
Fired when CS2-SimpleAdmin is fully initialized and ready.
|
||||
|
||||
```csharp
|
||||
event Action? OnSimpleAdminReady
|
||||
```
|
||||
|
||||
**When Fired:**
|
||||
- After plugin load
|
||||
- After database initialization
|
||||
- After migrations complete
|
||||
- Menu system ready
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null) return;
|
||||
|
||||
// Subscribe to ready event
|
||||
_api.OnSimpleAdminReady += OnSimpleAdminReady;
|
||||
|
||||
// Also call directly for hot reload case
|
||||
OnSimpleAdminReady();
|
||||
}
|
||||
|
||||
private void OnSimpleAdminReady()
|
||||
{
|
||||
Logger.LogInformation("SimpleAdmin is ready!");
|
||||
|
||||
// Register menus (requires SimpleAdmin to be ready)
|
||||
RegisterMenus();
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
_api.OnSimpleAdminReady -= OnSimpleAdminReady;
|
||||
}
|
||||
```
|
||||
|
||||
**Best Practice:**
|
||||
Always call your handler directly after subscribing to handle hot reload:
|
||||
```csharp
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // ← Also call directly
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Penalty Events
|
||||
|
||||
### OnPlayerPenaltied
|
||||
|
||||
Fired when an **online player** receives a penalty.
|
||||
|
||||
```csharp
|
||||
event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltied
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
1. `PlayerInfo player` - Player who received penalty
|
||||
2. `PlayerInfo? admin` - Admin who issued penalty (null if console)
|
||||
3. `PenaltyType penaltyType` - Type of penalty
|
||||
4. `string reason` - Penalty reason
|
||||
5. `int duration` - Duration in minutes (0 = permanent)
|
||||
6. `int? penaltyId` - Database penalty ID (null if not stored)
|
||||
7. `int? serverId` - Server ID (null in single-server mode)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
var adminName = admin?.PlayerName ?? "Console";
|
||||
Logger.LogInformation($"{adminName} penaltied {player.PlayerName}: {type} ({duration}m) - {reason}");
|
||||
|
||||
// React to specific penalty types
|
||||
switch (type)
|
||||
{
|
||||
case PenaltyType.Ban:
|
||||
// Log ban to external system
|
||||
LogBanToWebhook(player, admin, reason, duration);
|
||||
break;
|
||||
|
||||
case PenaltyType.Warn:
|
||||
// Check warning count
|
||||
if (player.Warnings >= 3)
|
||||
{
|
||||
Logger.LogWarning($"{player.PlayerName} has {player.Warnings} warnings!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### OnPlayerPenaltiedAdded
|
||||
|
||||
Fired when a penalty is added to an **offline player** by SteamID.
|
||||
|
||||
```csharp
|
||||
event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltiedAdded
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
1. `SteamID steamId` - Target player's SteamID
|
||||
2. `PlayerInfo? admin` - Admin who issued penalty
|
||||
3. `PenaltyType penaltyType` - Type of penalty
|
||||
4. `string reason` - Penalty reason
|
||||
5. `int duration` - Duration in minutes
|
||||
6. `int? penaltyId` - Database penalty ID
|
||||
7. `int? serverId` - Server ID
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
|
||||
|
||||
private void OnPlayerPenaltiedAdded(
|
||||
SteamID steamId,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
var adminName = admin?.PlayerName ?? "Console";
|
||||
Logger.LogInformation($"Offline penalty: {adminName} -> SteamID {steamId}: {type} ({duration}m)");
|
||||
|
||||
// Log to external database or webhook
|
||||
if (type == PenaltyType.Ban)
|
||||
{
|
||||
LogOfflineBan(steamId, admin, reason, duration);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Admin Activity Events
|
||||
|
||||
### OnAdminShowActivity
|
||||
|
||||
Fired when an admin action is displayed to players.
|
||||
|
||||
```csharp
|
||||
event Action<string, string?, bool, object>? OnAdminShowActivity
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
1. `string messageKey` - Translation key for the message
|
||||
2. `string? callerName` - Admin name (null if console)
|
||||
3. `bool dontPublish` - If true, don't broadcast to other systems
|
||||
4. `object messageArgs` - Arguments for message formatting
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.OnAdminShowActivity += OnAdminShowActivity;
|
||||
|
||||
private void OnAdminShowActivity(
|
||||
string messageKey,
|
||||
string? callerName,
|
||||
bool dontPublish,
|
||||
object messageArgs)
|
||||
{
|
||||
if (dontPublish) return;
|
||||
|
||||
Logger.LogInformation($"Admin activity: {messageKey} by {callerName ?? "Console"}");
|
||||
|
||||
// Log to Discord, database, etc.
|
||||
LogAdminAction(messageKey, callerName, messageArgs);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### OnAdminToggleSilent
|
||||
|
||||
Fired when an admin toggles silent mode.
|
||||
|
||||
```csharp
|
||||
event Action<int, bool>? OnAdminToggleSilent
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
1. `int slot` - Player slot of admin
|
||||
2. `bool status` - New silent status (true = silent, false = normal)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.OnAdminToggleSilent += OnAdminToggleSilent;
|
||||
|
||||
private void OnAdminToggleSilent(int slot, bool status)
|
||||
{
|
||||
var player = Utilities.GetPlayerFromSlot(slot);
|
||||
if (player == null) return;
|
||||
|
||||
var statusText = status ? "enabled" : "disabled";
|
||||
Logger.LogInformation($"{player.PlayerName} {statusText} silent mode");
|
||||
|
||||
// Update UI or external systems
|
||||
UpdateAdminStatus(player, status);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Ban Logging System
|
||||
|
||||
```csharp
|
||||
public class BanLogger
|
||||
{
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
|
||||
public void Initialize(ICS2_SimpleAdminApi api)
|
||||
{
|
||||
_api = api;
|
||||
|
||||
// Subscribe to both ban events
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
if (type != PenaltyType.Ban) return;
|
||||
|
||||
// Log to file
|
||||
File.AppendAllText("bans.log",
|
||||
$"[{DateTime.Now}] {player.PlayerName} ({player.SteamId}) " +
|
||||
$"banned by {admin?.PlayerName ?? "Console"} " +
|
||||
$"for {duration} minutes: {reason}\n");
|
||||
|
||||
// Send to Discord webhook
|
||||
SendDiscordNotification(
|
||||
$"🔨 **Ban Issued**\n" +
|
||||
$"Player: {player.PlayerName}\n" +
|
||||
$"Admin: {admin?.PlayerName ?? "Console"}\n" +
|
||||
$"Duration: {FormatDuration(duration)}\n" +
|
||||
$"Reason: {reason}"
|
||||
);
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltiedAdded(
|
||||
SteamID steamId,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
if (type != PenaltyType.Ban) return;
|
||||
|
||||
File.AppendAllText("bans.log",
|
||||
$"[{DateTime.Now}] Offline ban: SteamID {steamId} " +
|
||||
$"by {admin?.PlayerName ?? "Console"} " +
|
||||
$"for {duration} minutes: {reason}\n");
|
||||
}
|
||||
|
||||
private string FormatDuration(int minutes)
|
||||
{
|
||||
if (minutes == 0) return "Permanent";
|
||||
if (minutes < 60) return $"{minutes} minutes";
|
||||
if (minutes < 1440) return $"{minutes / 60} hours";
|
||||
return $"{minutes / 1440} days";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Warning Escalation System
|
||||
|
||||
```csharp
|
||||
public class WarningEscalation
|
||||
{
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
|
||||
public void Initialize(ICS2_SimpleAdminApi api)
|
||||
{
|
||||
_api = api;
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
// Only handle warnings
|
||||
if (type != PenaltyType.Warn) return;
|
||||
|
||||
Logger.LogInformation($"{player.PlayerName} now has {player.Warnings} warnings");
|
||||
|
||||
// Auto-escalate based on warning count
|
||||
if (player.Warnings >= 3)
|
||||
{
|
||||
// 3 warnings = 1 hour ban
|
||||
_api.IssuePenalty(
|
||||
GetPlayerController(player.SteamId),
|
||||
null,
|
||||
PenaltyType.Ban,
|
||||
"Automatic: 3 warnings",
|
||||
60
|
||||
);
|
||||
}
|
||||
else if (player.Warnings >= 5)
|
||||
{
|
||||
// 5 warnings = 1 day ban
|
||||
_api.IssuePenalty(
|
||||
GetPlayerController(player.SteamId),
|
||||
null,
|
||||
PenaltyType.Ban,
|
||||
"Automatic: 5 warnings",
|
||||
1440
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Admin Activity Monitor
|
||||
|
||||
```csharp
|
||||
public class AdminMonitor
|
||||
{
|
||||
private readonly Dictionary<string, int> _adminActions = new();
|
||||
|
||||
public void Initialize(ICS2_SimpleAdminApi api)
|
||||
{
|
||||
api.OnAdminShowActivity += OnAdminShowActivity;
|
||||
}
|
||||
|
||||
private void OnAdminShowActivity(
|
||||
string messageKey,
|
||||
string? callerName,
|
||||
bool dontPublish,
|
||||
object messageArgs)
|
||||
{
|
||||
if (callerName == null) return; // Ignore console actions
|
||||
|
||||
// Track admin actions
|
||||
if (!_adminActions.ContainsKey(callerName))
|
||||
{
|
||||
_adminActions[callerName] = 0;
|
||||
}
|
||||
|
||||
_adminActions[callerName]++;
|
||||
|
||||
// Log every 10th action
|
||||
if (_adminActions[callerName] % 10 == 0)
|
||||
{
|
||||
Logger.LogInformation(
|
||||
$"{callerName} has performed {_adminActions[callerName]} admin actions"
|
||||
);
|
||||
}
|
||||
|
||||
// Alert if admin is very active
|
||||
if (_adminActions[callerName] > 100)
|
||||
{
|
||||
Logger.LogWarning($"{callerName} has performed many actions ({_adminActions[callerName]})");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Unsubscribe
|
||||
|
||||
```csharp
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null) return;
|
||||
|
||||
// Subscribe
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
_api.OnAdminShowActivity += OnAdminShowActivity;
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// ALWAYS unsubscribe
|
||||
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
|
||||
_api.OnAdminShowActivity -= OnAdminShowActivity;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Handle Null Admins
|
||||
|
||||
```csharp
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin, // ← Can be null!
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
var adminName = admin?.PlayerName ?? "Console";
|
||||
// Use adminName safely
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Use Events for Integration
|
||||
|
||||
```csharp
|
||||
// ✅ Good - React to penalties
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
if (type == PenaltyType.Ban)
|
||||
{
|
||||
NotifyExternalSystem(player, reason);
|
||||
}
|
||||
};
|
||||
|
||||
// ❌ Bad - Wrapping penalty methods
|
||||
// Don't wrap IssuePenalty, use events instead
|
||||
```
|
||||
|
||||
### 4. Check Event Parameters
|
||||
|
||||
```csharp
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
// Check nullable parameters
|
||||
if (penaltyId.HasValue)
|
||||
{
|
||||
Logger.LogInformation($"Penalty ID: {penaltyId.Value}");
|
||||
}
|
||||
|
||||
if (serverId.HasValue)
|
||||
{
|
||||
Logger.LogInformation($"Server ID: {serverId.Value}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. OnSimpleAdminReady Pattern
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Handles both normal load and hot reload
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus();
|
||||
|
||||
// ❌ Bad - Only works on normal load
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Event-Based Statistics
|
||||
|
||||
```csharp
|
||||
public class ServerStatistics
|
||||
{
|
||||
private int _totalBans;
|
||||
private int _totalMutes;
|
||||
private int _totalWarnings;
|
||||
|
||||
public void Initialize(ICS2_SimpleAdminApi api)
|
||||
{
|
||||
api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case PenaltyType.Ban:
|
||||
_totalBans++;
|
||||
break;
|
||||
case PenaltyType.Mute:
|
||||
case PenaltyType.Gag:
|
||||
case PenaltyType.Silence:
|
||||
_totalMutes++;
|
||||
break;
|
||||
case PenaltyType.Warn:
|
||||
_totalWarnings++;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void PrintStatistics()
|
||||
{
|
||||
Logger.LogInformation($"Server Statistics:");
|
||||
Logger.LogInformation($"Total Bans: {_totalBans}");
|
||||
Logger.LogInformation($"Total Mutes: {_totalMutes}");
|
||||
Logger.LogInformation($"Total Warnings: {_totalWarnings}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Event Handling
|
||||
|
||||
```csharp
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
// Only handle bans
|
||||
if (type != PenaltyType.Ban) return;
|
||||
|
||||
// Only handle permanent bans
|
||||
if (duration != 0) return;
|
||||
|
||||
// Only handle admin-issued bans
|
||||
if (admin == null) return;
|
||||
|
||||
// Process permanent admin bans
|
||||
NotifyImportantBan(player, admin, reason);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Async Operations in Events
|
||||
|
||||
```csharp
|
||||
// ⚠️ Be careful with async in event handlers
|
||||
_api.OnPlayerPenaltied += async (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
// Don't block the game thread
|
||||
await Task.Run(() =>
|
||||
{
|
||||
// Long-running operation
|
||||
LogToExternalDatabase(player, type, reason);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### Efficient Event Handlers
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Quick processing
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
// Quick logging
|
||||
Logger.LogInformation($"Ban: {player.PlayerName}");
|
||||
};
|
||||
|
||||
// ❌ Bad - Heavy processing
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
// Don't do expensive operations synchronously
|
||||
SendEmailNotification(player); // ← This blocks the game thread!
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Event Not Firing
|
||||
|
||||
**Check:**
|
||||
1. Did you subscribe to the event?
|
||||
2. Is `_api` not null?
|
||||
3. Are you testing the right scenario?
|
||||
4. Check server console for errors
|
||||
|
||||
### Memory Leaks
|
||||
|
||||
**Always unsubscribe:**
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Unsubscribe ALL events
|
||||
_api.OnSimpleAdminReady -= OnReady;
|
||||
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
|
||||
// ... etc
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related APIs
|
||||
|
||||
- **[Penalties API](penalties)** - Issue penalties that trigger events
|
||||
- **[Commands API](commands)** - Commands that may trigger admin activity
|
||||
- **[Utilities API](utilities)** - Helper functions for event handlers
|
||||
706
CS2-SimpleAdmin-docs/docs/developer/api/menus.md
Normal file
@@ -0,0 +1,706 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Menus API
|
||||
|
||||
Complete reference for creating interactive admin menus.
|
||||
|
||||
## Menu Categories
|
||||
|
||||
### RegisterMenuCategory
|
||||
|
||||
Register a new menu category in the admin menu.
|
||||
|
||||
```csharp
|
||||
void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic")
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `categoryId` - Unique identifier for the category
|
||||
- `categoryName` - Display name shown in menu
|
||||
- `permission` - Required permission to see category (default: "@css/generic")
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.RegisterMenuCategory("mycategory", "My Custom Category", "@css/generic");
|
||||
```
|
||||
|
||||
**Best Practice:**
|
||||
Register categories in the `OnSimpleAdminReady` event handler:
|
||||
|
||||
```csharp
|
||||
_api.OnSimpleAdminReady += () =>
|
||||
{
|
||||
_api.RegisterMenuCategory("mycategory", Localizer?["category_name"] ?? "My Category");
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Menu Registration
|
||||
|
||||
### RegisterMenu (Basic)
|
||||
|
||||
Register a menu within a category.
|
||||
|
||||
```csharp
|
||||
void RegisterMenu(
|
||||
string categoryId,
|
||||
string menuId,
|
||||
string menuName,
|
||||
Func<CCSPlayerController, object> menuFactory,
|
||||
string? permission = null,
|
||||
string? commandName = null
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `categoryId` - Category to add menu to
|
||||
- `menuId` - Unique menu identifier
|
||||
- `menuName` - Display name in menu
|
||||
- `menuFactory` - Function that creates the menu
|
||||
- `permission` - Required permission (optional)
|
||||
- `commandName` - Command for permission override (optional, e.g., "css_god")
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.RegisterMenu(
|
||||
"mycategory",
|
||||
"mymenu",
|
||||
"My Menu",
|
||||
CreateMyMenu,
|
||||
"@css/generic"
|
||||
);
|
||||
|
||||
private object CreateMyMenu(CCSPlayerController player)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack("My Menu", "mycategory", player);
|
||||
// Add options...
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### RegisterMenu (with MenuContext) ⭐ RECOMMENDED
|
||||
|
||||
Register a menu with automatic context passing - **eliminates duplication!**
|
||||
|
||||
```csharp
|
||||
void RegisterMenu(
|
||||
string categoryId,
|
||||
string menuId,
|
||||
string menuName,
|
||||
Func<CCSPlayerController, MenuContext, object> menuFactory,
|
||||
string? permission = null,
|
||||
string? commandName = null
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `categoryId` - Category to add menu to
|
||||
- `menuId` - Unique menu identifier
|
||||
- `menuName` - Display name in menu
|
||||
- `menuFactory` - Function that receives player AND context
|
||||
- `permission` - Required permission (optional)
|
||||
- `commandName` - Command for permission override (optional)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
// ✅ NEW WAY - No duplication!
|
||||
_api.RegisterMenu(
|
||||
"fun",
|
||||
"god",
|
||||
"God Mode",
|
||||
CreateGodMenu,
|
||||
"@css/cheats",
|
||||
"css_god"
|
||||
);
|
||||
|
||||
private object CreateGodMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// context contains: CategoryId, MenuId, MenuTitle, Permission, CommandName
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context, // ← Automatically uses "God Mode" and "fun"
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) => ToggleGod(admin, target)
|
||||
);
|
||||
}
|
||||
|
||||
// ❌ OLD WAY - Had to repeat "God Mode" and "fun"
|
||||
private object CreateGodMenuOld(CCSPlayerController admin)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
"God Mode", // ← Repeated from RegisterMenu
|
||||
"fun", // ← Repeated from RegisterMenu
|
||||
admin,
|
||||
filter,
|
||||
action
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**MenuContext Properties:**
|
||||
```csharp
|
||||
public class MenuContext
|
||||
{
|
||||
public string CategoryId { get; } // e.g., "fun"
|
||||
public string MenuId { get; } // e.g., "god"
|
||||
public string MenuTitle { get; } // e.g., "God Mode"
|
||||
public string? Permission { get; } // e.g., "@css/cheats"
|
||||
public string? CommandName { get; } // e.g., "css_god"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### UnregisterMenu
|
||||
|
||||
Remove a menu from a category.
|
||||
|
||||
```csharp
|
||||
void UnregisterMenu(string categoryId, string menuId)
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
_api?.UnregisterMenu("mycategory", "mymenu");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Menu Creation
|
||||
|
||||
### CreateMenuWithBack
|
||||
|
||||
Create a menu with automatic back button.
|
||||
|
||||
```csharp
|
||||
object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `title` - Menu title
|
||||
- `categoryId` - Category for back button navigation
|
||||
- `player` - Player viewing the menu
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var menu = _api!.CreateMenuWithBack("My Menu", "mycategory", admin);
|
||||
_api.AddMenuOption(menu, "Option 1", _ => DoAction1());
|
||||
_api.AddMenuOption(menu, "Option 2", _ => DoAction2());
|
||||
return menu;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CreateMenuWithBack (with Context) ⭐ RECOMMENDED
|
||||
|
||||
Create a menu using context - **no duplication!**
|
||||
|
||||
```csharp
|
||||
object CreateMenuWithBack(MenuContext context, CCSPlayerController player)
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
private object CreateMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// ✅ Uses context.MenuTitle and context.CategoryId automatically
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
_api.AddMenuOption(menu, "Option 1", _ => DoAction1());
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CreateMenuWithPlayers
|
||||
|
||||
Create a menu showing a list of players.
|
||||
|
||||
```csharp
|
||||
object CreateMenuWithPlayers(
|
||||
string title,
|
||||
string categoryId,
|
||||
CCSPlayerController admin,
|
||||
Func<CCSPlayerController, bool> filter,
|
||||
Action<CCSPlayerController, CCSPlayerController> onSelect
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `title` - Menu title
|
||||
- `categoryId` - Category for back button
|
||||
- `admin` - Admin viewing menu
|
||||
- `filter` - Function to filter which players to show
|
||||
- `onSelect` - Action when player is selected (admin, selectedPlayer)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
"Select Player",
|
||||
"mycategory",
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) =>
|
||||
{
|
||||
// Do something with selected player
|
||||
DoAction(admin, target);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CreateMenuWithPlayers (with Context) ⭐ RECOMMENDED
|
||||
|
||||
```csharp
|
||||
object CreateMenuWithPlayers(
|
||||
MenuContext context,
|
||||
CCSPlayerController admin,
|
||||
Func<CCSPlayerController, bool> filter,
|
||||
Action<CCSPlayerController, CCSPlayerController> onSelect
|
||||
)
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
private object CreatePlayerMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context, // ← Automatically uses correct title and category!
|
||||
admin,
|
||||
player => player.PawnIsAlive && admin.CanTarget(player),
|
||||
(admin, target) => PerformAction(admin, target)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Menu Options
|
||||
|
||||
### AddMenuOption
|
||||
|
||||
Add a clickable option to a menu.
|
||||
|
||||
```csharp
|
||||
void AddMenuOption(
|
||||
object menu,
|
||||
string name,
|
||||
Action<CCSPlayerController> action,
|
||||
bool disabled = false,
|
||||
string? permission = null
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `menu` - Menu to add option to
|
||||
- `name` - Option display text
|
||||
- `action` - Function called when selected
|
||||
- `disabled` - Whether option is disabled (default: false)
|
||||
- `permission` - Required permission to see option (optional)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var menu = _api!.CreateMenuWithBack("Actions", "mycategory", admin);
|
||||
|
||||
_api.AddMenuOption(menu, "Heal Player", _ =>
|
||||
{
|
||||
target.SetHp(100);
|
||||
});
|
||||
|
||||
_api.AddMenuOption(menu, "Admin Only Option", _ =>
|
||||
{
|
||||
// Admin action
|
||||
}, false, "@css/root");
|
||||
|
||||
return menu;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### AddSubMenu
|
||||
|
||||
Add a submenu option that opens another menu.
|
||||
|
||||
```csharp
|
||||
void AddSubMenu(
|
||||
object menu,
|
||||
string name,
|
||||
Func<CCSPlayerController, object> subMenuFactory,
|
||||
bool disabled = false,
|
||||
string? permission = null
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `menu` - Parent menu
|
||||
- `name` - Submenu option display text
|
||||
- `subMenuFactory` - Function that creates the submenu
|
||||
- `disabled` - Whether option is disabled (default: false)
|
||||
- `permission` - Required permission (optional)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var menu = _api!.CreateMenuWithBack("Main Menu", "mycategory", admin);
|
||||
|
||||
_api.AddSubMenu(menu, "Player Actions", admin =>
|
||||
{
|
||||
return CreatePlayerActionsMenu(admin);
|
||||
});
|
||||
|
||||
_api.AddSubMenu(menu, "Server Settings", admin =>
|
||||
{
|
||||
return CreateServerSettingsMenu(admin);
|
||||
}, false, "@css/root");
|
||||
|
||||
return menu;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Opening Menus
|
||||
|
||||
### OpenMenu
|
||||
|
||||
Display a menu to a player.
|
||||
|
||||
```csharp
|
||||
void OpenMenu(object menu, CCSPlayerController player)
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var menu = CreateMyMenu(player);
|
||||
_api!.OpenMenu(menu, player);
|
||||
```
|
||||
|
||||
**Note:** Usually menus open automatically when selected, but this can be used for direct opening.
|
||||
|
||||
---
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Simple Player Selection Menu
|
||||
|
||||
```csharp
|
||||
private void RegisterMenus()
|
||||
{
|
||||
_api!.RegisterMenuCategory("actions", "Player Actions", "@css/generic");
|
||||
|
||||
_api.RegisterMenu(
|
||||
"actions",
|
||||
"slay",
|
||||
"Slay Player",
|
||||
CreateSlayMenu,
|
||||
"@css/slay"
|
||||
);
|
||||
}
|
||||
|
||||
private object CreateSlayMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context,
|
||||
admin,
|
||||
player => player.PawnIsAlive && admin.CanTarget(player),
|
||||
(admin, target) =>
|
||||
{
|
||||
target.PlayerPawn?.Value?.CommitSuicide(false, true);
|
||||
admin.PrintToChat($"Slayed {target.PlayerName}");
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nested Menu with Value Selection
|
||||
|
||||
```csharp
|
||||
private object CreateSetHpMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => p.PawnIsAlive && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
_api.AddSubMenu(menu, player.PlayerName, admin =>
|
||||
{
|
||||
return CreateHpValueMenu(admin, player);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack($"Set HP: {target.PlayerName}", "mycategory", admin);
|
||||
|
||||
var hpValues = new[] { 1, 10, 50, 100, 200, 500 };
|
||||
|
||||
foreach (var hp in hpValues)
|
||||
{
|
||||
_api.AddMenuOption(menu, $"{hp} HP", _ =>
|
||||
{
|
||||
if (target.IsValid && target.PawnIsAlive)
|
||||
{
|
||||
target.PlayerPawn?.Value?.SetHealth(hp);
|
||||
admin.PrintToChat($"Set {target.PlayerName} HP to {hp}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Menu with Permissions
|
||||
|
||||
```csharp
|
||||
private object CreateAdminMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
// Everyone with menu access sees this
|
||||
_api.AddMenuOption(menu, "Basic Action", _ => DoBasicAction());
|
||||
|
||||
// Only root admins see this
|
||||
_api.AddMenuOption(menu, "Dangerous Action", _ =>
|
||||
{
|
||||
DoDangerousAction();
|
||||
}, false, "@css/root");
|
||||
|
||||
// Submenu with permission
|
||||
_api.AddSubMenu(menu, "Advanced Options", admin =>
|
||||
{
|
||||
return CreateAdvancedMenu(admin);
|
||||
}, false, "@css/root");
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Dynamic Menu with Current State
|
||||
|
||||
```csharp
|
||||
private object CreateToggleMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
// Show current state in option name
|
||||
bool hasGod = GodPlayers.Contains(player.Slot);
|
||||
string status = hasGod ? "✓ ON" : "✗ OFF";
|
||||
|
||||
_api.AddMenuOption(menu, $"{player.PlayerName} ({status})", _ =>
|
||||
{
|
||||
if (hasGod)
|
||||
GodPlayers.Remove(player.Slot);
|
||||
else
|
||||
GodPlayers.Add(player.Slot);
|
||||
|
||||
// Recreate menu to show updated state
|
||||
var newMenu = CreateToggleMenu(admin, context);
|
||||
_api.OpenMenu(newMenu, admin);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use MenuContext
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Uses context
|
||||
_api.RegisterMenu("cat", "id", "Title", CreateMenu, "@css/generic");
|
||||
|
||||
private object CreateMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers(context, admin, filter, action);
|
||||
}
|
||||
|
||||
// ❌ Bad - Duplicates title and category
|
||||
_api.RegisterMenu("cat", "id", "Title", CreateMenuOld, "@css/generic");
|
||||
|
||||
private object CreateMenuOld(CCSPlayerController admin)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers("Title", "cat", admin, filter, action);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Register in OnSimpleAdminReady
|
||||
|
||||
```csharp
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // Also call directly for hot reload
|
||||
|
||||
private void RegisterMenus()
|
||||
{
|
||||
if (_menusRegistered) return;
|
||||
|
||||
_api!.RegisterMenuCategory("category", "Category Name");
|
||||
_api.RegisterMenu("category", "menu", "Menu Name", CreateMenu);
|
||||
|
||||
_menusRegistered = true;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Always Unregister
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
_api.UnregisterMenu("category", "menu");
|
||||
_api.OnSimpleAdminReady -= RegisterMenus;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Validate Player State
|
||||
|
||||
```csharp
|
||||
private object CreateMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context,
|
||||
admin,
|
||||
player => player.IsValid && // Player exists
|
||||
!player.IsBot && // Not a bot
|
||||
player.PawnIsAlive && // Alive
|
||||
admin.CanTarget(player), // Can be targeted
|
||||
(admin, target) =>
|
||||
{
|
||||
// Extra validation before action
|
||||
if (!target.IsValid || !target.PawnIsAlive)
|
||||
return;
|
||||
|
||||
DoAction(admin, target);
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Use Translations for Menu Names
|
||||
|
||||
```csharp
|
||||
_api.RegisterMenuCategory(
|
||||
"mycategory",
|
||||
Localizer?["category_name"] ?? "Default Name",
|
||||
"@css/generic"
|
||||
);
|
||||
|
||||
_api.RegisterMenu(
|
||||
"mycategory",
|
||||
"mymenu",
|
||||
Localizer?["menu_name"] ?? "Default Menu",
|
||||
CreateMenu
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Permission Override
|
||||
|
||||
The `commandName` parameter allows server admins to override menu permissions via CounterStrikeSharp's admin system.
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
_api.RegisterMenu(
|
||||
"fun",
|
||||
"god",
|
||||
"God Mode",
|
||||
CreateGodMenu,
|
||||
"@css/cheats", // Default permission
|
||||
"css_god" // Command name for override
|
||||
);
|
||||
```
|
||||
|
||||
**Admin config can override:**
|
||||
```json
|
||||
{
|
||||
"css_god": ["@css/vip"]
|
||||
}
|
||||
```
|
||||
|
||||
Now VIPs will see the God Mode menu instead of requiring @css/cheats!
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Player List with Actions
|
||||
|
||||
```csharp
|
||||
private object CreatePlayerListMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
foreach (var player in _api.GetValidPlayers())
|
||||
{
|
||||
if (!admin.CanTarget(player)) continue;
|
||||
|
||||
_api.AddSubMenu(menu, player.PlayerName, admin =>
|
||||
{
|
||||
var actionMenu = _api.CreateMenuWithBack($"Actions: {player.PlayerName}", context.CategoryId, admin);
|
||||
|
||||
_api.AddMenuOption(actionMenu, "Slay", _ => player.CommitSuicide());
|
||||
_api.AddMenuOption(actionMenu, "Kick", _ => KickPlayer(player));
|
||||
_api.AddMenuOption(actionMenu, "Ban", _ => BanPlayer(admin, player));
|
||||
|
||||
return actionMenu;
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
### Category-Based Organization
|
||||
|
||||
```csharp
|
||||
private void RegisterAllMenus()
|
||||
{
|
||||
// Player management category
|
||||
_api!.RegisterMenuCategory("players", "Player Management", "@css/generic");
|
||||
_api.RegisterMenu("players", "kick", "Kick Player", CreateKickMenu, "@css/kick");
|
||||
_api.RegisterMenu("players", "ban", "Ban Player", CreateBanMenu, "@css/ban");
|
||||
|
||||
// Server management category
|
||||
_api.RegisterMenuCategory("server", "Server Management", "@css/generic");
|
||||
_api.RegisterMenu("server", "map", "Change Map", CreateMapMenu, "@css/changemap");
|
||||
_api.RegisterMenu("server", "settings", "Settings", CreateSettingsMenu, "@css/root");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related APIs
|
||||
|
||||
- **[Commands API](commands)** - Command integration
|
||||
- **[Penalties API](penalties)** - Issue penalties from menus
|
||||
- **[Utilities API](utilities)** - Helper functions for menus
|
||||
621
CS2-SimpleAdmin-docs/docs/developer/api/overview.md
Normal file
@@ -0,0 +1,621 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# API Overview
|
||||
|
||||
Complete reference for the CS2-SimpleAdmin API (ICS2_SimpleAdminApi).
|
||||
|
||||
## Introduction
|
||||
|
||||
The CS2-SimpleAdmin API is exposed via the `ICS2_SimpleAdminApi` interface, accessible through CounterStrikeSharp's capability system.
|
||||
|
||||
---
|
||||
|
||||
## Getting the API
|
||||
|
||||
### Using Capability System
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
public class YourPlugin : BasePlugin
|
||||
{
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability =
|
||||
new("simpleadmin:api");
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found!");
|
||||
Unload(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// API is ready to use
|
||||
RegisterFeatures();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Static Capability Reference
|
||||
|
||||
```csharp
|
||||
// Alternative approach
|
||||
var capability = ICS2_SimpleAdminApi.PluginCapability;
|
||||
var api = capability?.Get();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Categories
|
||||
|
||||
The API is organized into logical categories:
|
||||
|
||||
| Category | Description | Learn More |
|
||||
|----------|-------------|------------|
|
||||
| **Commands** | Register/unregister commands, parse targets | [→](commands) |
|
||||
| **Menus** | Create admin menus with player selection | [→](menus) |
|
||||
| **Penalties** | Issue bans, mutes, gags, warnings | [→](penalties) |
|
||||
| **Events** | Subscribe to plugin events | [→](events) |
|
||||
| **Utilities** | Helper functions, player info, activity messages | [→](utilities) |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Command Management
|
||||
|
||||
```csharp
|
||||
// Register command
|
||||
_api.RegisterCommand(name, description, callback);
|
||||
|
||||
// Unregister command
|
||||
_api.UnRegisterCommand(name);
|
||||
|
||||
// Parse player targets
|
||||
var targets = _api.GetTarget(command);
|
||||
|
||||
// Log command
|
||||
_api.LogCommand(caller, command);
|
||||
```
|
||||
|
||||
**[Full Documentation →](commands)**
|
||||
|
||||
---
|
||||
|
||||
### Menu System
|
||||
|
||||
```csharp
|
||||
// Register category
|
||||
_api.RegisterMenuCategory(categoryId, categoryName, permission);
|
||||
|
||||
// Register menu
|
||||
_api.RegisterMenu(categoryId, menuId, menuName, menuFactory, permission, commandName);
|
||||
|
||||
// Create menu with players
|
||||
_api.CreateMenuWithPlayers(context, admin, filter, onSelect);
|
||||
|
||||
// Create menu with back button
|
||||
_api.CreateMenuWithBack(context, admin);
|
||||
|
||||
// Add menu option
|
||||
_api.AddMenuOption(menu, name, action, disabled, permission);
|
||||
|
||||
// Add submenu
|
||||
_api.AddSubMenu(menu, name, subMenuFactory, disabled, permission);
|
||||
|
||||
// Open menu
|
||||
_api.OpenMenu(menu, player);
|
||||
|
||||
// Unregister menu
|
||||
_api.UnregisterMenu(categoryId, menuId);
|
||||
```
|
||||
|
||||
**[Full Documentation →](menus)**
|
||||
|
||||
---
|
||||
|
||||
### Penalty Management
|
||||
|
||||
```csharp
|
||||
// Issue penalty to online player
|
||||
_api.IssuePenalty(player, admin, penaltyType, reason, duration);
|
||||
|
||||
// Issue penalty by SteamID
|
||||
_api.IssuePenalty(steamId, admin, penaltyType, reason, duration);
|
||||
|
||||
// Get player info
|
||||
var playerInfo = _api.GetPlayerInfo(player);
|
||||
|
||||
// Get mute status
|
||||
var muteStatus = _api.GetPlayerMuteStatus(player);
|
||||
```
|
||||
|
||||
**Penalty Types:**
|
||||
- `PenaltyType.Ban` - Ban player
|
||||
- `PenaltyType.Kick` - Kick player
|
||||
- `PenaltyType.Gag` - Block text chat
|
||||
- `PenaltyType.Mute` - Block voice chat
|
||||
- `PenaltyType.Silence` - Block both
|
||||
- `PenaltyType.Warn` - Issue warning
|
||||
|
||||
**[Full Documentation →](penalties)**
|
||||
|
||||
---
|
||||
|
||||
### Event System
|
||||
|
||||
```csharp
|
||||
// Plugin ready event
|
||||
_api.OnSimpleAdminReady += OnReady;
|
||||
|
||||
// Player penaltied
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
|
||||
// Offline penalty added
|
||||
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
|
||||
|
||||
// Admin activity
|
||||
_api.OnAdminShowActivity += OnAdminActivity;
|
||||
|
||||
// Admin silent toggle
|
||||
_api.OnAdminToggleSilent += OnAdminToggleSilent;
|
||||
```
|
||||
|
||||
**[Full Documentation →](events)**
|
||||
|
||||
---
|
||||
|
||||
### Utility Functions
|
||||
|
||||
```csharp
|
||||
// Get player info with penalties
|
||||
var info = _api.GetPlayerInfo(player);
|
||||
|
||||
// Get database connection string
|
||||
var connectionString = _api.GetConnectionString();
|
||||
|
||||
// Get server address
|
||||
var serverAddress = _api.GetServerAddress();
|
||||
|
||||
// Get server ID
|
||||
var serverId = _api.GetServerId();
|
||||
|
||||
// Get valid players
|
||||
var players = _api.GetValidPlayers();
|
||||
|
||||
// Check if admin is silent
|
||||
bool isSilent = _api.IsAdminSilent(player);
|
||||
|
||||
// Get all silent admins
|
||||
var silentAdmins = _api.ListSilentAdminsSlots();
|
||||
|
||||
// Show admin activity
|
||||
_api.ShowAdminActivity(messageKey, callerName, dontPublish, args);
|
||||
|
||||
// Show admin activity with custom translation
|
||||
_api.ShowAdminActivityTranslated(translatedMessage, callerName, dontPublish);
|
||||
|
||||
// Show admin activity with module localizer (recommended)
|
||||
_api.ShowAdminActivityLocalized(moduleLocalizer, messageKey, callerName, dontPublish, args);
|
||||
```
|
||||
|
||||
**[Full Documentation →](utilities)**
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Basic Module Structure
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
namespace MyModule;
|
||||
|
||||
public class MyModule : BasePlugin
|
||||
{
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability =
|
||||
new("simpleadmin:api");
|
||||
|
||||
public override string ModuleName => "My Module";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null) return;
|
||||
|
||||
// Register features
|
||||
RegisterCommands();
|
||||
|
||||
// Wait for SimpleAdmin ready
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // Fallback for hot reload
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Cleanup
|
||||
_api.UnRegisterCommand("css_mycommand");
|
||||
_api.UnregisterMenu("category", "menu");
|
||||
_api.OnSimpleAdminReady -= RegisterMenus;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Command with Target Selection
|
||||
|
||||
```csharp
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Parse targets
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
// Filter valid players
|
||||
var players = targets.Players
|
||||
.Where(p => p.IsValid && !p.IsBot && caller!.CanTarget(p))
|
||||
.ToList();
|
||||
|
||||
// Process each player
|
||||
foreach (var player in players)
|
||||
{
|
||||
DoSomethingToPlayer(caller, player);
|
||||
}
|
||||
|
||||
// Log command
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Menu with Player Selection
|
||||
|
||||
```csharp
|
||||
private object CreateMyMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// Context contains categoryId, menuId, menuName, permission, commandName
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context, // Automatic title and category
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) =>
|
||||
{
|
||||
// Action when player selected
|
||||
PerformAction(admin, target);
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nested Menu
|
||||
|
||||
```csharp
|
||||
private object CreatePlayerMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
_api.AddSubMenu(menu, player.PlayerName, admin =>
|
||||
{
|
||||
return CreateActionMenu(admin, player);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateActionMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack($"Actions for {target.PlayerName}", "category", admin);
|
||||
|
||||
_api.AddMenuOption(menu, "Action 1", _ => DoAction1(admin, target));
|
||||
_api.AddMenuOption(menu, "Action 2", _ => DoAction2(admin, target));
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Issue Penalty
|
||||
|
||||
```csharp
|
||||
private void BanPlayer(CCSPlayerController? admin, CCSPlayerController target, int duration, string reason)
|
||||
{
|
||||
// Issue ban
|
||||
_api!.IssuePenalty(
|
||||
target,
|
||||
admin,
|
||||
PenaltyType.Ban,
|
||||
reason,
|
||||
duration // minutes, 0 = permanent
|
||||
);
|
||||
|
||||
// Show activity
|
||||
if (admin == null || !_api.IsAdminSilent(admin))
|
||||
{
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"ban_message",
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
target.PlayerName,
|
||||
duration
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Event Subscription
|
||||
|
||||
```csharp
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null) return;
|
||||
|
||||
// Subscribe to events
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
Logger.LogInformation($"{player.PlayerName} received {type}: {reason} ({duration} min)");
|
||||
|
||||
// React to penalty
|
||||
if (type == PenaltyType.Ban)
|
||||
{
|
||||
// Handle ban
|
||||
}
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Check for Null
|
||||
|
||||
```csharp
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("API not available!");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Use OnSimpleAdminReady Event
|
||||
|
||||
```csharp
|
||||
_api.OnSimpleAdminReady += () =>
|
||||
{
|
||||
// Register menus only when SimpleAdmin is ready
|
||||
RegisterMenus();
|
||||
};
|
||||
|
||||
// Also call directly for hot reload case
|
||||
RegisterMenus();
|
||||
```
|
||||
|
||||
### 3. Clean Up on Unload
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Unregister all commands
|
||||
_api.UnRegisterCommand("css_mycommand");
|
||||
|
||||
// Unregister all menus
|
||||
_api.UnregisterMenu("category", "menu");
|
||||
|
||||
// Unsubscribe all events
|
||||
_api.OnSimpleAdminReady -= OnReady;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Validate Player State
|
||||
|
||||
```csharp
|
||||
if (!player.IsValid || !player.PawnIsAlive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!caller.CanTarget(player))
|
||||
{
|
||||
return; // Immunity check
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Use Per-Player Translations
|
||||
|
||||
```csharp
|
||||
// Each player sees message in their configured language
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer, // Your module's localizer
|
||||
"translation_key",
|
||||
caller?.PlayerName,
|
||||
false,
|
||||
args
|
||||
);
|
||||
```
|
||||
|
||||
### 6. Log All Admin Actions
|
||||
|
||||
```csharp
|
||||
_api.LogCommand(caller, command);
|
||||
// or
|
||||
_api.LogCommand(caller, $"css_mycommand {player.PlayerName}");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Versioning
|
||||
|
||||
The API uses semantic versioning:
|
||||
- **Major** - Breaking changes
|
||||
- **Minor** - New features, backwards compatible
|
||||
- **Patch** - Bug fixes
|
||||
|
||||
**Current Version:** Check [GitHub Releases](https://github.com/daffyyyy/CS2-SimpleAdmin/releases)
|
||||
|
||||
---
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The API is designed for single-threaded use within the CounterStrikeSharp game thread.
|
||||
|
||||
**Do NOT:**
|
||||
- Call API methods from background threads
|
||||
- Use async/await with API calls without proper synchronization
|
||||
|
||||
**Do:**
|
||||
- Call API methods from event handlers
|
||||
- Call API methods from commands
|
||||
- Call API methods from timers
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
The API uses exceptions for critical errors:
|
||||
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
_api.RegisterCommand("css_cmd", "Desc", callback);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Logger.LogError($"Failed to register command: {ex.Message}");
|
||||
}
|
||||
```
|
||||
|
||||
**Common exceptions:**
|
||||
- `ArgumentException` - Invalid arguments
|
||||
- `InvalidOperationException` - Invalid state
|
||||
- `KeyNotFoundException` - Player not found
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Efficient Player Filtering
|
||||
|
||||
```csharp
|
||||
// ✅ Good - single LINQ query
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => p.IsValid && admin.CanTarget(p))
|
||||
.ToList();
|
||||
|
||||
// ❌ Bad - multiple iterations
|
||||
var players = _api.GetValidPlayers();
|
||||
players = players.Where(p => p.IsValid).ToList();
|
||||
players = players.Where(p => admin.CanTarget(p)).ToList();
|
||||
```
|
||||
|
||||
### Cache Expensive Operations
|
||||
|
||||
```csharp
|
||||
// Cache menu creation if used multiple times
|
||||
private object? _cachedMenu;
|
||||
|
||||
private object GetMenu(CCSPlayerController player)
|
||||
{
|
||||
if (_cachedMenu == null)
|
||||
{
|
||||
_cachedMenu = CreateMenu(player);
|
||||
}
|
||||
return _cachedMenu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
### Enable Detailed Logging
|
||||
|
||||
```csharp
|
||||
Logger.LogInformation("Debug: API loaded");
|
||||
Logger.LogWarning("Warning: Player not found");
|
||||
Logger.LogError("Error: Failed to execute command");
|
||||
```
|
||||
|
||||
### Check API Availability
|
||||
|
||||
```csharp
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("❌ CS2-SimpleAdmin API not found!");
|
||||
Logger.LogError("Make sure CS2-SimpleAdmin is installed and loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInformation("✅ CS2-SimpleAdmin API loaded successfully");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Commands API](commands)** - Command registration and targeting
|
||||
- **[Menus API](menus)** - Menu system details
|
||||
- **[Penalties API](penalties)** - Penalty management
|
||||
- **[Events API](events)** - Event subscription
|
||||
- **[Utilities API](utilities)** - Helper functions
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **[GitHub Repository](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
|
||||
- **[Fun Commands Module](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** - Reference implementation
|
||||
- **[Module Development Guide](../module/getting-started)** - Create modules
|
||||
610
CS2-SimpleAdmin-docs/docs/developer/api/penalties.md
Normal file
@@ -0,0 +1,610 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Penalties API
|
||||
|
||||
Complete reference for issuing and managing player penalties.
|
||||
|
||||
## Penalty Types
|
||||
|
||||
```csharp
|
||||
public enum PenaltyType
|
||||
{
|
||||
Ban, // Ban player from server
|
||||
Kick, // Kick player from server
|
||||
Gag, // Block text chat
|
||||
Mute, // Block voice chat
|
||||
Silence, // Block both text and voice
|
||||
Warn // Issue warning
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Issue Penalties
|
||||
|
||||
### IssuePenalty (Online Player)
|
||||
|
||||
Issue a penalty to a currently connected player.
|
||||
|
||||
```csharp
|
||||
void IssuePenalty(
|
||||
CCSPlayerController player,
|
||||
CCSPlayerController? admin,
|
||||
PenaltyType penaltyType,
|
||||
string reason,
|
||||
int duration = -1
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `player` - Target player controller
|
||||
- `admin` - Admin issuing penalty (null for console)
|
||||
- `penaltyType` - Type of penalty
|
||||
- `reason` - Reason for penalty
|
||||
- `duration` - Duration in minutes (0 = permanent, -1 = default)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
// Ban player for 1 day
|
||||
_api!.IssuePenalty(
|
||||
player,
|
||||
admin,
|
||||
PenaltyType.Ban,
|
||||
"Cheating",
|
||||
1440 // 24 hours in minutes
|
||||
);
|
||||
|
||||
// Permanent ban
|
||||
_api.IssuePenalty(
|
||||
player,
|
||||
admin,
|
||||
PenaltyType.Ban,
|
||||
"Severe rule violation",
|
||||
0
|
||||
);
|
||||
|
||||
// Kick player
|
||||
_api.IssuePenalty(
|
||||
player,
|
||||
admin,
|
||||
PenaltyType.Kick,
|
||||
"AFK"
|
||||
);
|
||||
|
||||
// Gag for 30 minutes
|
||||
_api.IssuePenalty(
|
||||
player,
|
||||
admin,
|
||||
PenaltyType.Gag,
|
||||
"Chat spam",
|
||||
30
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### IssuePenalty (Offline Player)
|
||||
|
||||
Issue a penalty to a player by SteamID (even if offline).
|
||||
|
||||
```csharp
|
||||
void IssuePenalty(
|
||||
SteamID steamid,
|
||||
CCSPlayerController? admin,
|
||||
PenaltyType penaltyType,
|
||||
string reason,
|
||||
int duration = -1
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `steamid` - Target player's SteamID
|
||||
- `admin` - Admin issuing penalty (null for console)
|
||||
- `penaltyType` - Type of penalty
|
||||
- `reason` - Reason for penalty
|
||||
- `duration` - Duration in minutes (0 = permanent, -1 = default)
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
// Ban offline player
|
||||
var steamId = new SteamID(76561198012345678);
|
||||
|
||||
_api!.IssuePenalty(
|
||||
steamId,
|
||||
admin,
|
||||
PenaltyType.Ban,
|
||||
"Ban evasion",
|
||||
10080 // 7 days
|
||||
);
|
||||
|
||||
// Mute offline player
|
||||
_api.IssuePenalty(
|
||||
steamId,
|
||||
admin,
|
||||
PenaltyType.Mute,
|
||||
"Voice abuse",
|
||||
1440
|
||||
);
|
||||
```
|
||||
|
||||
**Supported SteamID Formats:**
|
||||
```csharp
|
||||
// SteamID64
|
||||
new SteamID(76561198012345678)
|
||||
|
||||
// Also works with SteamID string parsing
|
||||
SteamID.FromString("STEAM_1:0:12345678")
|
||||
SteamID.FromString("[U:1:12345678]")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get Player Information
|
||||
|
||||
### GetPlayerInfo
|
||||
|
||||
Get detailed player information including penalty counts.
|
||||
|
||||
```csharp
|
||||
PlayerInfo GetPlayerInfo(CCSPlayerController player)
|
||||
```
|
||||
|
||||
**Returns:** `PlayerInfo` object containing:
|
||||
- `PlayerName` - Player's name
|
||||
- `SteamId` - Steam ID (ulong)
|
||||
- `IpAddress` - Player's IP address
|
||||
- `Warnings` - Warning count
|
||||
- `Bans` - Ban count
|
||||
- `Mutes` - Mute count
|
||||
- `Gags` - Gag count
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var playerInfo = _api!.GetPlayerInfo(player);
|
||||
|
||||
Console.WriteLine($"Player: {playerInfo.PlayerName}");
|
||||
Console.WriteLine($"SteamID: {playerInfo.SteamId}");
|
||||
Console.WriteLine($"Warnings: {playerInfo.Warnings}");
|
||||
Console.WriteLine($"Total Bans: {playerInfo.Bans}");
|
||||
|
||||
// Check if player has penalties
|
||||
if (playerInfo.Warnings >= 3)
|
||||
{
|
||||
_api.IssuePenalty(player, null, PenaltyType.Ban, "Too many warnings", 1440);
|
||||
}
|
||||
```
|
||||
|
||||
**Throws:**
|
||||
- `KeyNotFoundException` - If player doesn't have a valid UserId
|
||||
|
||||
---
|
||||
|
||||
### GetPlayerMuteStatus
|
||||
|
||||
Get current mute/gag/silence status for a player.
|
||||
|
||||
```csharp
|
||||
Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetPlayerMuteStatus(
|
||||
CCSPlayerController player
|
||||
)
|
||||
```
|
||||
|
||||
**Returns:** Dictionary mapping penalty types to lists of active penalties
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var muteStatus = _api!.GetPlayerMuteStatus(player);
|
||||
|
||||
// Check if player is gagged
|
||||
if (muteStatus.ContainsKey(PenaltyType.Gag))
|
||||
{
|
||||
var gagPenalties = muteStatus[PenaltyType.Gag];
|
||||
|
||||
foreach (var (endTime, duration, passed) in gagPenalties)
|
||||
{
|
||||
if (!passed)
|
||||
{
|
||||
var remaining = endTime - DateTime.UtcNow;
|
||||
Console.WriteLine($"Gagged for {remaining.TotalMinutes:F0} more minutes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if player is muted
|
||||
if (muteStatus.ContainsKey(PenaltyType.Mute))
|
||||
{
|
||||
Console.WriteLine("Player is currently muted");
|
||||
}
|
||||
|
||||
// Check if player is silenced
|
||||
if (muteStatus.ContainsKey(PenaltyType.Silence))
|
||||
{
|
||||
Console.WriteLine("Player is silenced (gag + mute)");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Server Information
|
||||
|
||||
### GetConnectionString
|
||||
|
||||
Get the database connection string.
|
||||
|
||||
```csharp
|
||||
string GetConnectionString()
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var connectionString = _api!.GetConnectionString();
|
||||
// Use for custom database operations
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GetServerAddress
|
||||
|
||||
Get the server's IP address and port.
|
||||
|
||||
```csharp
|
||||
string GetServerAddress()
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var serverAddress = _api!.GetServerAddress();
|
||||
Console.WriteLine($"Server: {serverAddress}");
|
||||
// Example output: "192.168.1.100:27015"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GetServerId
|
||||
|
||||
Get the server's unique ID in the database.
|
||||
|
||||
```csharp
|
||||
int? GetServerId()
|
||||
```
|
||||
|
||||
**Returns:**
|
||||
- `int` - Server ID if multi-server mode enabled
|
||||
- `null` - If single-server mode
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var serverId = _api!.GetServerId();
|
||||
|
||||
if (serverId.HasValue)
|
||||
{
|
||||
Console.WriteLine($"Server ID: {serverId.Value}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Single-server mode");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Ban with Validation
|
||||
|
||||
```csharp
|
||||
private void BanPlayer(CCSPlayerController? admin, CCSPlayerController target, int duration, string reason)
|
||||
{
|
||||
// Validate player
|
||||
if (!target.IsValid)
|
||||
{
|
||||
admin?.PrintToChat("Invalid player!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check immunity
|
||||
if (admin != null && !admin.CanTarget(target))
|
||||
{
|
||||
admin.PrintToChat($"You cannot ban {target.PlayerName} (higher immunity)!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get player info to check history
|
||||
var playerInfo = _api!.GetPlayerInfo(target);
|
||||
|
||||
Logger.LogInformation(
|
||||
$"{admin?.PlayerName ?? "Console"} banning {playerInfo.PlayerName} " +
|
||||
$"(SteamID: {playerInfo.SteamId}, Previous bans: {playerInfo.Bans})"
|
||||
);
|
||||
|
||||
// Issue ban
|
||||
_api.IssuePenalty(target, admin, PenaltyType.Ban, reason, duration);
|
||||
|
||||
// Show activity
|
||||
if (admin == null || !_api.IsAdminSilent(admin))
|
||||
{
|
||||
var durationText = duration == 0 ? "permanently" : $"for {duration} minutes";
|
||||
Server.PrintToChatAll($"{admin?.PlayerName ?? "Console"} banned {target.PlayerName} {durationText}: {reason}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Progressive Punishment System
|
||||
|
||||
```csharp
|
||||
private void HandlePlayerOffense(CCSPlayerController? admin, CCSPlayerController target, string reason)
|
||||
{
|
||||
var playerInfo = _api!.GetPlayerInfo(target);
|
||||
|
||||
// Progressive punishment based on warning count
|
||||
if (playerInfo.Warnings == 0)
|
||||
{
|
||||
// First offense - warning
|
||||
_api.IssuePenalty(target, admin, PenaltyType.Warn, reason);
|
||||
target.PrintToChat("This is your first warning!");
|
||||
}
|
||||
else if (playerInfo.Warnings == 1)
|
||||
{
|
||||
// Second offense - gag for 30 minutes
|
||||
_api.IssuePenalty(target, admin, PenaltyType.Gag, $"Second offense: {reason}", 30);
|
||||
target.PrintToChat("Second warning! You are gagged for 30 minutes.");
|
||||
}
|
||||
else if (playerInfo.Warnings == 2)
|
||||
{
|
||||
// Third offense - 1 day ban
|
||||
_api.IssuePenalty(target, admin, PenaltyType.Ban, $"Third offense: {reason}", 1440);
|
||||
target.PrintToChat("Third offense! You are banned for 1 day.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// More than 3 warnings - permanent ban
|
||||
_api.IssuePenalty(target, admin, PenaltyType.Ban, $"Multiple offenses: {reason}", 0);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Check Active Penalties Before Action
|
||||
|
||||
```csharp
|
||||
private void AllowPlayerToChat(CCSPlayerController player)
|
||||
{
|
||||
var muteStatus = _api!.GetPlayerMuteStatus(player);
|
||||
|
||||
// Check if player is gagged
|
||||
if (muteStatus.ContainsKey(PenaltyType.Gag))
|
||||
{
|
||||
player.PrintToChat("You are currently gagged and cannot use chat!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if player is silenced (includes gag)
|
||||
if (muteStatus.ContainsKey(PenaltyType.Silence))
|
||||
{
|
||||
player.PrintToChat("You are silenced and cannot communicate!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Player can chat
|
||||
ProcessChatMessage(player);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Offline Player Ban
|
||||
|
||||
```csharp
|
||||
private void BanOfflinePlayer(CCSPlayerController? admin, string steamIdString, int duration, string reason)
|
||||
{
|
||||
// Parse SteamID
|
||||
if (!ulong.TryParse(steamIdString, out ulong steamId64))
|
||||
{
|
||||
admin?.PrintToChat("Invalid SteamID format!");
|
||||
return;
|
||||
}
|
||||
|
||||
var steamId = new SteamID(steamId64);
|
||||
|
||||
// Issue offline ban
|
||||
_api!.IssuePenalty(steamId, admin, PenaltyType.Ban, reason, duration);
|
||||
|
||||
Logger.LogInformation(
|
||||
$"{admin?.PlayerName ?? "Console"} banned offline player " +
|
||||
$"(SteamID: {steamId64}) for {duration} minutes: {reason}"
|
||||
);
|
||||
|
||||
admin?.PrintToChat($"Offline ban issued to SteamID {steamId64}");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Multi-Account Detection
|
||||
|
||||
```csharp
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (player == null || !player.IsValid) return HookResult.Continue;
|
||||
|
||||
var playerInfo = _api!.GetPlayerInfo(player);
|
||||
|
||||
// Check if player has multiple accounts
|
||||
if (playerInfo.Bans > 0)
|
||||
{
|
||||
// Notify admins
|
||||
var admins = Utilities.GetPlayers()
|
||||
.Where(p => AdminManager.PlayerHasPermissions(p, "@css/ban"));
|
||||
|
||||
foreach (var admin in admins)
|
||||
{
|
||||
admin.PrintToChat(
|
||||
$"⚠ {player.PlayerName} has {playerInfo.Bans} previous ban(s)!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Validate Players
|
||||
|
||||
```csharp
|
||||
if (!target.IsValid || !target.PawnIsAlive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check immunity
|
||||
if (admin != null && !admin.CanTarget(target))
|
||||
{
|
||||
admin.PrintToChat("Cannot target this player!");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Provide Clear Reasons
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Specific reason
|
||||
_api.IssuePenalty(player, admin, PenaltyType.Ban, "Aimbot detected in Round 12", 10080);
|
||||
|
||||
// ❌ Bad - Vague reason
|
||||
_api.IssuePenalty(player, admin, PenaltyType.Ban, "cheating", 10080);
|
||||
```
|
||||
|
||||
### 3. Log Penalty Actions
|
||||
|
||||
```csharp
|
||||
_api.IssuePenalty(player, admin, PenaltyType.Ban, reason, duration);
|
||||
|
||||
Logger.LogInformation(
|
||||
$"Penalty issued: {admin?.PlayerName ?? "Console"} -> {player.PlayerName} " +
|
||||
$"| Type: {PenaltyType.Ban} | Duration: {duration}m | Reason: {reason}"
|
||||
);
|
||||
```
|
||||
|
||||
### 4. Handle Kick Separately
|
||||
|
||||
```csharp
|
||||
// Kick doesn't need duration
|
||||
_api.IssuePenalty(player, admin, PenaltyType.Kick, reason);
|
||||
|
||||
// NOT:
|
||||
_api.IssuePenalty(player, admin, PenaltyType.Kick, reason, 0);
|
||||
```
|
||||
|
||||
### 5. Check Active Penalties
|
||||
|
||||
```csharp
|
||||
// Before issuing new penalty, check existing ones
|
||||
var muteStatus = _api.GetPlayerMuteStatus(player);
|
||||
|
||||
if (muteStatus.ContainsKey(PenaltyType.Gag))
|
||||
{
|
||||
admin?.PrintToChat($"{player.PlayerName} is already gagged!");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Duration Helpers
|
||||
|
||||
```csharp
|
||||
public static class PenaltyDurations
|
||||
{
|
||||
public const int OneHour = 60;
|
||||
public const int OneDay = 1440;
|
||||
public const int OneWeek = 10080;
|
||||
public const int TwoWeeks = 20160;
|
||||
public const int OneMonth = 43200;
|
||||
public const int Permanent = 0;
|
||||
}
|
||||
|
||||
// Usage
|
||||
_api.IssuePenalty(player, admin, PenaltyType.Ban, reason, PenaltyDurations.OneWeek);
|
||||
```
|
||||
|
||||
### Penalty History Display
|
||||
|
||||
```csharp
|
||||
private void ShowPlayerHistory(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var info = _api!.GetPlayerInfo(target);
|
||||
|
||||
admin.PrintToChat($"=== {info.PlayerName} History ===");
|
||||
admin.PrintToChat($"Warnings: {info.Warnings}");
|
||||
admin.PrintToChat($"Bans: {info.Bans}");
|
||||
admin.PrintToChat($"Mutes: {info.Mutes}");
|
||||
admin.PrintToChat($"Gags: {info.Gags}");
|
||||
|
||||
var muteStatus = _api.GetPlayerMuteStatus(target);
|
||||
|
||||
if (muteStatus.ContainsKey(PenaltyType.Gag))
|
||||
admin.PrintToChat("Currently: GAGGED");
|
||||
if (muteStatus.ContainsKey(PenaltyType.Mute))
|
||||
admin.PrintToChat("Currently: MUTED");
|
||||
if (muteStatus.ContainsKey(PenaltyType.Silence))
|
||||
admin.PrintToChat("Currently: SILENCED");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Handle Invalid Players
|
||||
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
var playerInfo = _api!.GetPlayerInfo(player);
|
||||
// Use playerInfo...
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
Logger.LogError($"Player info not found for {player?.PlayerName}");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### Validate SteamID
|
||||
|
||||
```csharp
|
||||
private bool TryParseSteamId(string input, out SteamID steamId)
|
||||
{
|
||||
steamId = default;
|
||||
|
||||
if (ulong.TryParse(input, out ulong steamId64))
|
||||
{
|
||||
steamId = new SteamID(steamId64);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related APIs
|
||||
|
||||
- **[Commands API](commands)** - Issue penalties from commands
|
||||
- **[Menus API](menus)** - Issue penalties from menus
|
||||
- **[Events API](events)** - React to penalty events
|
||||
- **[Utilities API](utilities)** - Helper functions
|
||||
585
CS2-SimpleAdmin-docs/docs/developer/api/utilities.md
Normal file
@@ -0,0 +1,585 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Utilities API
|
||||
|
||||
Helper functions and utility methods for module development.
|
||||
|
||||
## Player Management
|
||||
|
||||
### GetValidPlayers
|
||||
|
||||
Get a list of all valid, connected players.
|
||||
|
||||
```csharp
|
||||
List<CCSPlayerController> GetValidPlayers()
|
||||
```
|
||||
|
||||
**Returns:** List of valid player controllers
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var players = _api!.GetValidPlayers();
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
Console.WriteLine($"Player: {player.PlayerName}");
|
||||
}
|
||||
|
||||
// Filter for specific criteria
|
||||
var alivePlayers = _api.GetValidPlayers()
|
||||
.Where(p => p.PawnIsAlive)
|
||||
.ToList();
|
||||
|
||||
var ctPlayers = _api.GetValidPlayers()
|
||||
.Where(p => p.Team == CsTeam.CounterTerrorist)
|
||||
.ToList();
|
||||
```
|
||||
|
||||
**Note:** This method filters out invalid and bot players automatically.
|
||||
|
||||
---
|
||||
|
||||
## Admin Status
|
||||
|
||||
### IsAdminSilent
|
||||
|
||||
Check if an admin is in silent mode.
|
||||
|
||||
```csharp
|
||||
bool IsAdminSilent(CCSPlayerController player)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `player` - Player to check
|
||||
|
||||
**Returns:** `true` if player is in silent mode, `false` otherwise
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
private void PerformAdminAction(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
// Do the action
|
||||
DoAction(target);
|
||||
|
||||
// Only show activity if not silent
|
||||
if (!_api!.IsAdminSilent(admin))
|
||||
{
|
||||
Server.PrintToChatAll($"{admin.PlayerName} performed action on {target.PlayerName}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ListSilentAdminsSlots
|
||||
|
||||
Get a list of player slots for all admins currently in silent mode.
|
||||
|
||||
```csharp
|
||||
HashSet<int> ListSilentAdminsSlots()
|
||||
```
|
||||
|
||||
**Returns:** HashSet of player slots
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
var silentAdmins = _api!.ListSilentAdminsSlots();
|
||||
|
||||
Console.WriteLine($"Silent admins: {silentAdmins.Count}");
|
||||
|
||||
foreach (var slot in silentAdmins)
|
||||
{
|
||||
var player = Utilities.GetPlayerFromSlot(slot);
|
||||
if (player != null)
|
||||
{
|
||||
Console.WriteLine($"- {player.PlayerName} (slot {slot})");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Activity Messages
|
||||
|
||||
### ShowAdminActivity
|
||||
|
||||
Show an admin activity message to all players.
|
||||
|
||||
```csharp
|
||||
void ShowAdminActivity(
|
||||
string messageKey,
|
||||
string? callerName = null,
|
||||
bool dontPublish = false,
|
||||
params object[] messageArgs
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `messageKey` - Translation key from SimpleAdmin's lang files
|
||||
- `callerName` - Admin name (null for console)
|
||||
- `dontPublish` - If true, don't trigger OnAdminShowActivity event
|
||||
- `messageArgs` - Arguments for message formatting
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
// Using SimpleAdmin's built-in translations
|
||||
_api!.ShowAdminActivity(
|
||||
"sa_admin_player_kick_message", // Translation key
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
player.PlayerName,
|
||||
reason
|
||||
);
|
||||
```
|
||||
|
||||
**Limitations:**
|
||||
- Only works with SimpleAdmin's own translation keys
|
||||
- For module-specific messages, use `ShowAdminActivityLocalized`
|
||||
|
||||
---
|
||||
|
||||
### ShowAdminActivityTranslated
|
||||
|
||||
Show a pre-translated admin activity message.
|
||||
|
||||
```csharp
|
||||
void ShowAdminActivityTranslated(
|
||||
string translatedMessage,
|
||||
string? callerName = null,
|
||||
bool dontPublish = false
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `translatedMessage` - Already translated message
|
||||
- `callerName` - Admin name
|
||||
- `dontPublish` - If true, don't trigger event
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
// Use when you've already translated the message
|
||||
var message = Localizer?["my_action_message", player.PlayerName] ?? $"Action on {player.PlayerName}";
|
||||
|
||||
_api!.ShowAdminActivityTranslated(
|
||||
message,
|
||||
admin?.PlayerName,
|
||||
false
|
||||
);
|
||||
```
|
||||
|
||||
**Use Case:**
|
||||
- When you need custom message formatting
|
||||
- When translation is already done
|
||||
|
||||
---
|
||||
|
||||
### ShowAdminActivityLocalized ⭐ RECOMMENDED
|
||||
|
||||
Show admin activity with per-player language support using module's localizer.
|
||||
|
||||
```csharp
|
||||
void ShowAdminActivityLocalized(
|
||||
object moduleLocalizer,
|
||||
string messageKey,
|
||||
string? callerName = null,
|
||||
bool dontPublish = false,
|
||||
params object[] messageArgs
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `moduleLocalizer` - Your module's `IStringLocalizer` instance
|
||||
- `messageKey` - Translation key from your module's lang files
|
||||
- `callerName` - Admin name
|
||||
- `dontPublish` - If true, don't trigger event
|
||||
- `messageArgs` - Message arguments
|
||||
|
||||
**Example:**
|
||||
```csharp
|
||||
// Each player sees message in their configured language!
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(
|
||||
Localizer, // Your module's localizer
|
||||
"fun_admin_god_message", // From your lang/en.json
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
player.PlayerName
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**lang/en.json:**
|
||||
```json
|
||||
{
|
||||
"fun_admin_god_message": "{lightred}{0}{default} changed god mode for {lightred}{1}{default}!"
|
||||
}
|
||||
```
|
||||
|
||||
**Why This is Best:**
|
||||
- ✅ Each player sees message in their own language
|
||||
- ✅ Uses your module's translations
|
||||
- ✅ Supports color codes
|
||||
- ✅ Per-player localization
|
||||
|
||||
---
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Action with Activity Message
|
||||
|
||||
```csharp
|
||||
private void ToggleGodMode(CCSPlayerController? admin, CCSPlayerController target)
|
||||
{
|
||||
// Perform action
|
||||
if (GodPlayers.Contains(target.Slot))
|
||||
{
|
||||
GodPlayers.Remove(target.Slot);
|
||||
}
|
||||
else
|
||||
{
|
||||
GodPlayers.Add(target.Slot);
|
||||
}
|
||||
|
||||
// Show activity (respecting silent mode)
|
||||
if (admin == null || !_api!.IsAdminSilent(admin))
|
||||
{
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"fun_admin_god_message",
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
target.PlayerName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Log action
|
||||
_api!.LogCommand(admin, $"css_god {target.PlayerName}");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Broadcast to Non-Silent Admins
|
||||
|
||||
```csharp
|
||||
private void NotifyAdmins(string message)
|
||||
{
|
||||
var silentAdmins = _api!.ListSilentAdminsSlots();
|
||||
|
||||
var players = _api.GetValidPlayers();
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
// Check if player is admin
|
||||
if (!AdminManager.PlayerHasPermissions(player, "@css/generic"))
|
||||
continue;
|
||||
|
||||
// Skip if admin is in silent mode
|
||||
if (silentAdmins.Contains(player.Slot))
|
||||
continue;
|
||||
|
||||
player.PrintToChat(message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Filter Players by Criteria
|
||||
|
||||
```csharp
|
||||
private List<CCSPlayerController> GetTargetablePlayers(CCSPlayerController admin)
|
||||
{
|
||||
return _api!.GetValidPlayers()
|
||||
.Where(p =>
|
||||
p.IsValid &&
|
||||
!p.IsBot &&
|
||||
p.PawnIsAlive &&
|
||||
admin.CanTarget(p))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private List<CCSPlayerController> GetAliveEnemies(CCSPlayerController player)
|
||||
{
|
||||
return _api!.GetValidPlayers()
|
||||
.Where(p =>
|
||||
p.Team != player.Team &&
|
||||
p.PawnIsAlive)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private List<CCSPlayerController> GetAdmins()
|
||||
{
|
||||
return _api!.GetValidPlayers()
|
||||
.Where(p => AdminManager.PlayerHasPermissions(p, "@css/generic"))
|
||||
.ToList();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use ShowAdminActivityLocalized
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Per-player language
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"my_message_key",
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
args
|
||||
);
|
||||
|
||||
// ❌ Bad - Single language for all
|
||||
Server.PrintToChatAll($"{admin?.PlayerName} did something");
|
||||
```
|
||||
|
||||
### 2. Respect Silent Mode
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Check silent mode
|
||||
if (admin == null || !_api.IsAdminSilent(admin))
|
||||
{
|
||||
ShowActivity();
|
||||
}
|
||||
|
||||
// ❌ Bad - Always show activity
|
||||
ShowActivity(); // Ignores silent mode!
|
||||
```
|
||||
|
||||
### 3. Validate Players from GetValidPlayers
|
||||
|
||||
```csharp
|
||||
var players = _api.GetValidPlayers();
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
// Still good to check, especially for async operations
|
||||
if (!player.IsValid) continue;
|
||||
|
||||
DoSomething(player);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Cache Silent Admin List if Checking Multiple Times
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Cache for multiple checks
|
||||
var silentAdmins = _api.ListSilentAdminsSlots();
|
||||
|
||||
foreach (var admin in admins)
|
||||
{
|
||||
if (silentAdmins.Contains(admin.Slot)) continue;
|
||||
NotifyAdmin(admin);
|
||||
}
|
||||
|
||||
// ❌ Bad - Query for each admin
|
||||
foreach (var admin in admins)
|
||||
{
|
||||
if (_api.IsAdminSilent(admin)) continue; // ← Repeated calls
|
||||
NotifyAdmin(admin);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Silent Mode Wrapper
|
||||
|
||||
```csharp
|
||||
private void ShowActivityIfNotSilent(
|
||||
CCSPlayerController? admin,
|
||||
string messageKey,
|
||||
params object[] args)
|
||||
{
|
||||
if (admin != null && _api!.IsAdminSilent(admin))
|
||||
return;
|
||||
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
messageKey,
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
args
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
ShowActivityIfNotSilent(admin, "my_action", player.PlayerName);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Online Admins
|
||||
|
||||
```csharp
|
||||
private List<CCSPlayerController> GetOnlineAdmins(string permission = "@css/generic")
|
||||
{
|
||||
return _api!.GetValidPlayers()
|
||||
.Where(p => AdminManager.PlayerHasPermissions(p, permission))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Usage
|
||||
var admins = GetOnlineAdmins("@css/root");
|
||||
foreach (var admin in admins)
|
||||
{
|
||||
admin.PrintToChat("Important admin message");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Notify All Players Except Silent Admins
|
||||
|
||||
```csharp
|
||||
private void BroadcastMessage(string message, bool excludeSilentAdmins = true)
|
||||
{
|
||||
var silentAdmins = excludeSilentAdmins
|
||||
? _api!.ListSilentAdminsSlots()
|
||||
: new HashSet<int>();
|
||||
|
||||
foreach (var player in _api.GetValidPlayers())
|
||||
{
|
||||
if (silentAdmins.Contains(player.Slot))
|
||||
continue;
|
||||
|
||||
player.PrintToChat(message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Activity Message Formatting
|
||||
|
||||
### Color Codes in Messages
|
||||
|
||||
All activity messages support color codes:
|
||||
|
||||
```json
|
||||
{
|
||||
"my_message": "{lightred}Admin{default} banned {lightred}{0}{default} for {yellow}{1}{default}"
|
||||
}
|
||||
```
|
||||
|
||||
**Available Colors:**
|
||||
- `{default}` - Default color
|
||||
- `{white}` - White
|
||||
- `{darkred}` - Dark red
|
||||
- `{green}` - Green
|
||||
- `{lightyellow}` - Light yellow
|
||||
- `{lightblue}` - Light blue
|
||||
- `{olive}` - Olive
|
||||
- `{lime}` - Lime
|
||||
- `{red}` - Red
|
||||
- `{purple}` - Purple
|
||||
- `{grey}` - Grey
|
||||
- `{yellow}` - Yellow
|
||||
- `{gold}` - Gold
|
||||
- `{silver}` - Silver
|
||||
- `{blue}` - Blue
|
||||
- `{darkblue}` - Dark blue
|
||||
- `{bluegrey}` - Blue grey
|
||||
- `{magenta}` - Magenta
|
||||
- `{lightred}` - Light red
|
||||
- `{orange}` - Orange
|
||||
|
||||
---
|
||||
|
||||
### Message Arguments
|
||||
|
||||
```csharp
|
||||
// lang/en.json
|
||||
{
|
||||
"ban_message": "{lightred}{0}{default} banned {lightred}{1}{default} for {yellow}{2}{default} minutes: {red}{3}"
|
||||
}
|
||||
|
||||
// Code
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"ban_message",
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
admin?.PlayerName, // {0}
|
||||
target.PlayerName, // {1}
|
||||
duration, // {2}
|
||||
reason // {3}
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Tips
|
||||
|
||||
### Minimize GetValidPlayers Calls
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Call once, filter multiple times
|
||||
var allPlayers = _api.GetValidPlayers();
|
||||
var alivePlayers = allPlayers.Where(p => p.PawnIsAlive).ToList();
|
||||
var deadPlayers = allPlayers.Where(p => !p.PawnIsAlive).ToList();
|
||||
|
||||
// ❌ Bad - Multiple calls
|
||||
var alivePlayers = _api.GetValidPlayers().Where(p => p.PawnIsAlive).ToList();
|
||||
var deadPlayers = _api.GetValidPlayers().Where(p => !p.PawnIsAlive).ToList();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Efficient Filtering
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Single LINQ query
|
||||
var targets = _api.GetValidPlayers()
|
||||
.Where(p => p.Team == CsTeam.Terrorist &&
|
||||
p.PawnIsAlive &&
|
||||
admin.CanTarget(p))
|
||||
.ToList();
|
||||
|
||||
// ❌ Bad - Multiple iterations
|
||||
var players = _api.GetValidPlayers();
|
||||
players = players.Where(p => p.Team == CsTeam.Terrorist).ToList();
|
||||
players = players.Where(p => p.PawnIsAlive).ToList();
|
||||
players = players.Where(p => admin.CanTarget(p)).ToList();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Activity Messages Not Showing
|
||||
|
||||
**Check:**
|
||||
1. Is `Localizer` not null?
|
||||
2. Does translation key exist in lang files?
|
||||
3. Is message correctly formatted?
|
||||
4. Check `dontPublish` parameter
|
||||
|
||||
### Silent Mode Not Working
|
||||
|
||||
**Check:**
|
||||
1. Is player actually in silent mode? (`css_hide` command)
|
||||
2. Are you checking before showing activity?
|
||||
3. Check slot vs player controller mismatch
|
||||
|
||||
---
|
||||
|
||||
## Related APIs
|
||||
|
||||
- **[Commands API](commands)** - Log commands
|
||||
- **[Menus API](menus)** - Get players for menus
|
||||
- **[Events API](events)** - Admin activity events
|
||||
- **[Penalties API](penalties)** - Get player info
|
||||
695
CS2-SimpleAdmin-docs/docs/developer/architecture.md
Normal file
@@ -0,0 +1,695 @@
|
||||
---
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
# Plugin Architecture
|
||||
|
||||
Deep dive into CS2-SimpleAdmin's architecture and design patterns.
|
||||
|
||||
## Overview
|
||||
|
||||
CS2-SimpleAdmin follows a **layered architecture** with clear separation of concerns and well-defined responsibilities for each component.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Layers
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ CounterStrikeSharp Integration Layer │ ← CS2_SimpleAdmin.cs
|
||||
├─────────────────────────────────────────┤
|
||||
│ Manager Layer │ ← /Managers/
|
||||
│ • PermissionManager │
|
||||
│ • BanManager │
|
||||
│ • MuteManager │
|
||||
│ • WarnManager │
|
||||
│ • CacheManager │
|
||||
│ • PlayerManager │
|
||||
│ • ServerManager │
|
||||
│ • DiscordManager │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Database Layer │ ← /Database/
|
||||
│ • IDatabaseProvider (Interface) │
|
||||
│ • MySqlDatabaseProvider │
|
||||
│ • SqliteDatabaseProvider │
|
||||
│ • Migration System │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Menu System │ ← /Menus/
|
||||
│ • MenuManager (Singleton) │
|
||||
│ • MenuBuilder (Factory) │
|
||||
│ • Specific Menu Classes │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Command System │ ← /Commands/
|
||||
│ • RegisterCommands │
|
||||
│ • Command Handlers (basebans, etc.) │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Public API │ ← /Api/
|
||||
│ • ICS2_SimpleAdminApi (Interface) │
|
||||
│ • CS2_SimpleAdminApi (Implementation) │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. CounterStrikeSharp Integration Layer
|
||||
|
||||
**File:** `CS2_SimpleAdmin.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Plugin lifecycle management (Load/Unload)
|
||||
- Event registration (`player_connect`, `player_disconnect`, etc.)
|
||||
- Command routing
|
||||
- Low-level game operations using `MemoryFunctionVoid`
|
||||
- Timer management
|
||||
|
||||
**Key Methods:**
|
||||
```csharp
|
||||
public override void Load(bool hotReload)
|
||||
public override void Unload(bool hotReload)
|
||||
private HookResult OnPlayerConnect(EventPlayerConnectFull @event, GameEventInfo info)
|
||||
private HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Manager Layer
|
||||
|
||||
Each manager encapsulates specific domain logic:
|
||||
|
||||
#### PermissionManager
|
||||
|
||||
**File:** `/Managers/PermissionManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Load admin flags and groups from database
|
||||
- Maintain in-memory `AdminCache` with lazy-loading
|
||||
- Time-based cache expiry
|
||||
- Immunity level management
|
||||
|
||||
**Key Patterns:**
|
||||
- Caching for performance
|
||||
- Lazy loading of admin data
|
||||
- Periodic refresh
|
||||
|
||||
#### BanManager
|
||||
|
||||
**File:** `/Managers/BanManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Issue bans (SteamID, IP, or hybrid)
|
||||
- Remove bans (unban)
|
||||
- Handle ban expiration cleanup
|
||||
- Multi-server ban synchronization
|
||||
|
||||
**Key Operations:**
|
||||
```csharp
|
||||
Task BanPlayer(...)
|
||||
Task AddBanBySteamId(...)
|
||||
Task RemoveBan(...)
|
||||
```
|
||||
|
||||
#### MuteManager
|
||||
|
||||
**File:** `/Managers/MuteManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Three mute types: GAG (text), MUTE (voice), SILENCE (both)
|
||||
- Duration-based mutes
|
||||
- Expiration tracking
|
||||
|
||||
#### WarnManager
|
||||
|
||||
**File:** `/Managers/WarnManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Progressive warning system
|
||||
- Auto-escalation to bans based on `WarnThreshold` config
|
||||
- Warning history tracking
|
||||
|
||||
#### CacheManager
|
||||
|
||||
**File:** `/Managers/CacheManager.cs`
|
||||
|
||||
**Purpose:** Performance optimization layer
|
||||
|
||||
**Features:**
|
||||
- In-memory ban cache with O(1) lookups by SteamID and IP
|
||||
- Player IP history tracking for multi-account detection
|
||||
- Reduces database queries on player join
|
||||
|
||||
**Data Structures:**
|
||||
```csharp
|
||||
Dictionary<ulong, BanInfo> _banCacheBySteamId
|
||||
Dictionary<string, List<BanInfo>> _banCacheByIp
|
||||
Dictionary<ulong, List<string>> _playerIpHistory
|
||||
```
|
||||
|
||||
#### PlayerManager
|
||||
|
||||
**File:** `/Managers/PlayerManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Load player data on connect
|
||||
- Check bans against cache
|
||||
- Update IP history
|
||||
- Semaphore limiting (max 5 concurrent loads)
|
||||
|
||||
**Key Pattern:**
|
||||
```csharp
|
||||
private readonly SemaphoreSlim _semaphore = new(5, 5);
|
||||
|
||||
public async Task LoadPlayerData(CCSPlayerController player)
|
||||
{
|
||||
await _semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
// Load player data
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### ServerManager
|
||||
|
||||
**File:** `/Managers/ServerManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Load/register server metadata (IP, port, hostname, RCON)
|
||||
- Multi-server mode support
|
||||
- Server ID management
|
||||
|
||||
#### DiscordManager
|
||||
|
||||
**File:** `/Managers/DiscordManager.cs`
|
||||
|
||||
**Responsibilities:**
|
||||
- Send webhook notifications for admin actions
|
||||
- Configurable webhooks per penalty type
|
||||
- Embed formatting with placeholders
|
||||
|
||||
---
|
||||
|
||||
### 3. Database Layer
|
||||
|
||||
**Files:** `/Database/`
|
||||
|
||||
**Provider Pattern** for database abstraction:
|
||||
|
||||
```csharp
|
||||
public interface IDatabaseProvider
|
||||
{
|
||||
Task ExecuteAsync(string query, object? parameters = null);
|
||||
Task<T> QueryFirstOrDefaultAsync<T>(string query, object? parameters = null);
|
||||
Task<List<T>> QueryAsync<T>(string query, object? parameters = null);
|
||||
|
||||
// Query generation methods
|
||||
string GetBanQuery(bool multiServer);
|
||||
string GetMuteQuery(bool multiServer);
|
||||
// ... more query methods
|
||||
}
|
||||
```
|
||||
|
||||
**Implementations:**
|
||||
- `MySqlDatabaseProvider` - MySQL-specific SQL syntax
|
||||
- `SqliteDatabaseProvider` - SQLite-specific SQL syntax
|
||||
|
||||
**Benefits:**
|
||||
- Single codebase supports both MySQL and SQLite
|
||||
- Easy to add new database providers
|
||||
- Query methods accept `multiServer` boolean for scoping
|
||||
|
||||
**Migration System:**
|
||||
|
||||
**File:** `Database/Migration.cs`
|
||||
|
||||
- File-based migrations in `/Database/Migrations/{mysql,sqlite}/`
|
||||
- Numbered files: `001_CreateTables.sql`, `002_AddColumn.sql`
|
||||
- Tracking table: `sa_migrations`
|
||||
- Auto-applies on plugin load
|
||||
- Safe for multi-server environments
|
||||
|
||||
---
|
||||
|
||||
### 4. Menu System
|
||||
|
||||
**Files:** `/Menus/`
|
||||
|
||||
**MenuManager (Singleton Pattern):**
|
||||
|
||||
```csharp
|
||||
public class MenuManager
|
||||
{
|
||||
public static MenuManager Instance { get; private set; }
|
||||
|
||||
private readonly Dictionary<string, MenuCategory> _categories = new();
|
||||
private readonly Dictionary<string, Dictionary<string, MenuInfo>> _menus = new();
|
||||
|
||||
public void RegisterCategory(string id, string name, string permission);
|
||||
public void RegisterMenu(string categoryId, string menuId, string name, ...);
|
||||
public MenuBuilder CreateCategoryMenuPublic(MenuCategory category, CCSPlayerController player);
|
||||
}
|
||||
```
|
||||
|
||||
**MenuBuilder (Factory Pattern):**
|
||||
|
||||
```csharp
|
||||
public class MenuBuilder
|
||||
{
|
||||
public MenuBuilder(string title);
|
||||
public MenuBuilder AddOption(string name, Action<CCSPlayerController> action, ...);
|
||||
public MenuBuilder AddSubMenu(string name, Func<CCSPlayerController, MenuBuilder> factory, ...);
|
||||
public MenuBuilder WithBackAction(Action<CCSPlayerController> backAction);
|
||||
public void OpenMenu(CCSPlayerController player);
|
||||
}
|
||||
```
|
||||
|
||||
**Specific Menu Classes:**
|
||||
- `AdminMenu` - Main admin menu with categories
|
||||
- `ManagePlayersMenu` - Player management menus
|
||||
- `ManageServerMenu` - Server settings
|
||||
- `DurationMenu` - Duration selection
|
||||
- `ReasonMenu` - Reason selection
|
||||
|
||||
**Benefits:**
|
||||
- Centralized menu management
|
||||
- Permission-aware rendering
|
||||
- Automatic back button handling
|
||||
- Reusable menu components
|
||||
|
||||
---
|
||||
|
||||
### 5. Command System
|
||||
|
||||
**Files:** `/Commands/`
|
||||
|
||||
**Central Registration:**
|
||||
|
||||
**File:** `RegisterCommands.cs`
|
||||
|
||||
```csharp
|
||||
public static class RegisterCommands
|
||||
{
|
||||
public static Dictionary<string, List<CommandDefinition>> _commandDefinitions = new();
|
||||
|
||||
public static void RegisterCommands(CS2_SimpleAdmin plugin)
|
||||
{
|
||||
// Load Commands.json
|
||||
// Map commands to handler methods
|
||||
// Register with CounterStrikeSharp
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Command Handlers:**
|
||||
|
||||
Organized by category:
|
||||
- `basebans.cs` - Ban, unban, warn commands
|
||||
- `basecomms.cs` - Gag, mute, silence commands
|
||||
- `basecommands.cs` - Admin management, server commands
|
||||
- `basechat.cs` - Chat commands (asay, csay, etc.)
|
||||
- `playercommands.cs` - Player manipulation (slay, hp, etc.)
|
||||
- `funcommands.cs` - Fun commands (god, noclip, etc.)
|
||||
- `basevotes.cs` - Voting system
|
||||
|
||||
**Two-Tier Pattern:**
|
||||
|
||||
```csharp
|
||||
// Entry command - parses arguments
|
||||
[CommandHelper(2, "<#userid> <duration> [reason]")]
|
||||
[RequiresPermissions("@css/ban")]
|
||||
public void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = GetTarget(command);
|
||||
int duration = ParseDuration(command.GetArg(2));
|
||||
string reason = ParseReason(command);
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
Ban(caller, target, duration, reason); // Core method
|
||||
}
|
||||
}
|
||||
|
||||
// Core method - database writes, events
|
||||
private void Ban(CCSPlayerController? admin, CCSPlayerController target, int duration, string reason)
|
||||
{
|
||||
// Write to database
|
||||
BanManager.BanPlayer(target, admin, duration, reason);
|
||||
|
||||
// Update cache
|
||||
CacheManager.AddBan(target);
|
||||
|
||||
// Trigger events
|
||||
ApiInstance.OnPlayerPenaltiedEvent(target, admin, PenaltyType.Ban, reason, duration);
|
||||
|
||||
// Kick player
|
||||
Server.ExecuteCommand($"kick {target.UserId}");
|
||||
|
||||
// Send Discord notification
|
||||
DiscordManager.SendBanNotification(target, admin, duration, reason);
|
||||
|
||||
// Broadcast action
|
||||
ShowAdminActivity("ban_message", admin?.PlayerName, target.PlayerName, duration, reason);
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Separation of parsing and execution
|
||||
- Reusable core methods
|
||||
- Consistent event triggering
|
||||
- Easy to test
|
||||
|
||||
---
|
||||
|
||||
### 6. Public API
|
||||
|
||||
**Files:** `/Api/`
|
||||
|
||||
**Interface:** `ICS2_SimpleAdminApi.cs` (in CS2-SimpleAdminApi project)
|
||||
|
||||
**Implementation:** `CS2_SimpleAdminApi.cs`
|
||||
|
||||
**Capability System:**
|
||||
|
||||
```csharp
|
||||
// In API interface
|
||||
public static readonly PluginCapability<ICS2_SimpleAdminApi> PluginCapability = new("simpleadmin:api");
|
||||
|
||||
// In module
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
}
|
||||
```
|
||||
|
||||
**Event Publishing:**
|
||||
|
||||
```csharp
|
||||
// API exposes events
|
||||
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, ...>? OnPlayerPenaltied;
|
||||
|
||||
// Core plugin triggers events
|
||||
ApiInstance.OnPlayerPenaltiedEvent(player, admin, type, reason, duration, id);
|
||||
|
||||
// Modules subscribe
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
// React to penalty
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow Patterns
|
||||
|
||||
### Player Join Flow
|
||||
|
||||
```
|
||||
1. player_connect event
|
||||
↓
|
||||
2. PlayerManager.LoadPlayerData()
|
||||
↓
|
||||
3. Semaphore.WaitAsync() ← Max 5 concurrent
|
||||
↓
|
||||
4. CacheManager.CheckBan(steamId, ip)
|
||||
↓
|
||||
5a. BANNED → Kick player immediately
|
||||
5b. CLEAN → Continue
|
||||
↓
|
||||
6. Load active penalties from DB
|
||||
↓
|
||||
7. Store in PlayersInfo dictionary
|
||||
↓
|
||||
8. Update player IP history
|
||||
```
|
||||
|
||||
### Ban Command Flow
|
||||
|
||||
```
|
||||
1. OnBanCommand() ← Parse arguments
|
||||
↓
|
||||
2. Ban() ← Core method
|
||||
↓
|
||||
3. BanManager.BanPlayer() ← Write to DB
|
||||
↓
|
||||
4. CacheManager.AddBan() ← Update cache
|
||||
↓
|
||||
5. ApiInstance.OnPlayerPenaltiedEvent() ← Trigger event
|
||||
↓
|
||||
6. Server.ExecuteCommand("kick") ← Kick player
|
||||
↓
|
||||
7. DiscordManager.SendNotification() ← Discord webhook
|
||||
↓
|
||||
8. ShowAdminActivity() ← Broadcast action
|
||||
```
|
||||
|
||||
### Admin Permission Check Flow
|
||||
|
||||
```
|
||||
1. Plugin Load
|
||||
↓
|
||||
2. PermissionManager.LoadAdmins()
|
||||
↓
|
||||
3. Build AdminCache ← SteamID → Flags/Immunity
|
||||
↓
|
||||
4. Command Execution
|
||||
↓
|
||||
5. RequiresPermissions attribute check
|
||||
↓
|
||||
6. AdminManager.PlayerHasPermissions() ← Check cache
|
||||
↓
|
||||
7a. HAS PERMISSION → Execute
|
||||
7b. NO PERMISSION → Deny
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design Patterns Used
|
||||
|
||||
### Singleton Pattern
|
||||
|
||||
```csharp
|
||||
public class MenuManager
|
||||
{
|
||||
public static MenuManager Instance { get; private set; }
|
||||
|
||||
public static void Initialize(CS2_SimpleAdmin plugin)
|
||||
{
|
||||
Instance = new MenuManager(plugin);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Used for:**
|
||||
- MenuManager - Single menu registry
|
||||
- Cache management - Single source of truth
|
||||
|
||||
### Factory Pattern
|
||||
|
||||
```csharp
|
||||
public class MenuBuilder
|
||||
{
|
||||
public static MenuBuilder Create(string title) => new MenuBuilder(title);
|
||||
|
||||
public MenuBuilder AddOption(...) { /* ... */ return this; }
|
||||
public MenuBuilder AddSubMenu(...) { /* ... */ return this; }
|
||||
}
|
||||
```
|
||||
|
||||
**Used for:**
|
||||
- Menu creation
|
||||
- Database provider creation
|
||||
|
||||
### Strategy Pattern
|
||||
|
||||
```csharp
|
||||
public interface IDatabaseProvider
|
||||
{
|
||||
Task<List<BanInfo>> GetBans(bool multiServer);
|
||||
}
|
||||
|
||||
public class MySqlDatabaseProvider : IDatabaseProvider { /* ... */ }
|
||||
public class SqliteDatabaseProvider : IDatabaseProvider { /* ... */ }
|
||||
```
|
||||
|
||||
**Used for:**
|
||||
- Database abstraction
|
||||
- Query generation per DB type
|
||||
|
||||
### Observer Pattern
|
||||
|
||||
```csharp
|
||||
// Publisher
|
||||
public event Action<PlayerInfo, ...>? OnPlayerPenaltied;
|
||||
|
||||
// Trigger
|
||||
OnPlayerPenaltied?.Invoke(player, admin, type, reason, duration, id, sid);
|
||||
|
||||
// Subscribers
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, sid) =>
|
||||
{
|
||||
// React
|
||||
};
|
||||
```
|
||||
|
||||
**Used for:**
|
||||
- Event system
|
||||
- Module communication
|
||||
|
||||
---
|
||||
|
||||
## Concurrency & Thread Safety
|
||||
|
||||
### Async/Await Patterns
|
||||
|
||||
All database operations use `async`/`await`:
|
||||
|
||||
```csharp
|
||||
public async Task BanPlayer(...)
|
||||
{
|
||||
await _database.ExecuteAsync(query, parameters);
|
||||
}
|
||||
```
|
||||
|
||||
### Semaphore for Rate Limiting
|
||||
|
||||
```csharp
|
||||
private readonly SemaphoreSlim _semaphore = new(5, 5);
|
||||
|
||||
public async Task LoadPlayerData(CCSPlayerController player)
|
||||
{
|
||||
await _semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
// Load data
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Thread-Safe Collections
|
||||
|
||||
```csharp
|
||||
private readonly ConcurrentDictionary<ulong, PlayerInfo> PlayersInfo = new();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Memory Management
|
||||
|
||||
### In-Memory Caches
|
||||
|
||||
**AdminCache:**
|
||||
```csharp
|
||||
Dictionary<ulong, (List<string> Flags, int Immunity, DateTime Expiry)> AdminCache
|
||||
```
|
||||
|
||||
**BanCache:**
|
||||
```csharp
|
||||
Dictionary<ulong, BanInfo> _banCacheBySteamId
|
||||
Dictionary<string, List<BanInfo>> _banCacheByIp
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Reduces database load
|
||||
- O(1) lookups
|
||||
- TTL-based expiry
|
||||
|
||||
### Cleanup
|
||||
|
||||
```csharp
|
||||
// On player disconnect
|
||||
PlayersInfo.TryRemove(player.SteamID, out _);
|
||||
|
||||
// Periodic cache cleanup
|
||||
AddTimer(3600f, CleanupExpiredCache, TimerFlags.REPEAT);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration System
|
||||
|
||||
### Multi-Level Configuration
|
||||
|
||||
1. **Main Config:** `CS2-SimpleAdmin.json`
|
||||
2. **Commands Config:** `Commands.json`
|
||||
3. **Module Configs:** Per-module JSON files
|
||||
|
||||
### Hot Reload Support
|
||||
|
||||
```csharp
|
||||
public void OnConfigParsed(Config config)
|
||||
{
|
||||
Config = config;
|
||||
// Reconfigure without restart
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
1. **Caching** - Minimize database queries
|
||||
2. **Lazy Loading** - Load admin data on-demand
|
||||
3. **Semaphore** - Limit concurrent operations
|
||||
4. **Connection Pooling** - Reuse DB connections
|
||||
5. **Indexed Queries** - Fast database lookups
|
||||
6. **Memory Cleanup** - Remove disconnected player data
|
||||
|
||||
---
|
||||
|
||||
## Future Extensibility
|
||||
|
||||
### Plugin Capabilities
|
||||
|
||||
New modules can extend functionality:
|
||||
|
||||
```csharp
|
||||
// New capability
|
||||
var customCapability = new PluginCapability<ICustomFeature>("custom:feature");
|
||||
Capabilities.RegisterPluginCapability(customCapability, () => _customFeature);
|
||||
|
||||
// Other plugins can use it
|
||||
var feature = _customCapability.Get();
|
||||
```
|
||||
|
||||
### Event-Driven Architecture
|
||||
|
||||
New events can be added without breaking changes:
|
||||
|
||||
```csharp
|
||||
public event Action<NewEventArgs>? OnNewEvent;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Considerations
|
||||
|
||||
### Unit Testing
|
||||
|
||||
- Managers can be tested independently
|
||||
- Mock `IDatabaseProvider` for testing
|
||||
- Test command handlers with mock players
|
||||
|
||||
### Integration Testing
|
||||
|
||||
- Test on actual CS2 server
|
||||
- Multi-server scenarios
|
||||
- Database migration testing
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **[API Overview](api/overview)** - Public API details
|
||||
- **[Module Development](module/getting-started)** - Create modules
|
||||
- **[GitHub Source](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Browse code
|
||||
379
CS2-SimpleAdmin-docs/docs/developer/intro.md
Normal file
@@ -0,0 +1,379 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Developer Introduction
|
||||
|
||||
Welcome to the CS2-SimpleAdmin developer documentation!
|
||||
|
||||
## Overview
|
||||
|
||||
This section contains technical documentation for developers who want to:
|
||||
|
||||
- Create modules using the CS2-SimpleAdmin API
|
||||
- Contribute to the core plugin
|
||||
- Integrate with CS2-SimpleAdmin from other plugins
|
||||
- Understand the plugin architecture
|
||||
|
||||
---
|
||||
|
||||
## API Documentation
|
||||
|
||||
The CS2-SimpleAdmin API provides a rich set of features for module developers:
|
||||
|
||||
### Core Features
|
||||
|
||||
- **[Commands](api/commands)** - Register and manage commands
|
||||
- **[Menus](api/menus)** - Create admin menus with player selection
|
||||
- **[Penalties](api/penalties)** - Issue bans, mutes, gags, warnings
|
||||
- **[Events](api/events)** - Subscribe to plugin events
|
||||
- **[Utilities](api/utilities)** - Helper functions and player management
|
||||
|
||||
---
|
||||
|
||||
## Quick Links
|
||||
|
||||
### For Module Developers
|
||||
|
||||
- **[Module Development Guide](module/getting-started)** - Start creating modules
|
||||
- **[Best Practices](module/best-practices)** - Write better code
|
||||
- **[Examples](module/examples)** - Code examples and patterns
|
||||
|
||||
### For Core Contributors
|
||||
|
||||
- **[Architecture](architecture)** - Plugin structure and design
|
||||
- **[GitHub Repository](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- C# knowledge (intermediate level)
|
||||
- .NET 8.0 SDK
|
||||
- CounterStrikeSharp understanding
|
||||
- CS2 dedicated server for testing
|
||||
|
||||
### Development Environment
|
||||
|
||||
**Recommended:**
|
||||
- Visual Studio 2022 (Community or higher)
|
||||
- VS Code with C# extension
|
||||
- Git for version control
|
||||
|
||||
---
|
||||
|
||||
## CS2-SimpleAdminApi Interface
|
||||
|
||||
The main API interface provides all functionality:
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
// Get the API
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability =
|
||||
new("simpleadmin:api");
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the API
|
||||
_api.RegisterCommand("css_mycommand", "Description", OnMyCommand);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Categories
|
||||
|
||||
### Command Management
|
||||
|
||||
Register custom commands that integrate with CS2-SimpleAdmin:
|
||||
|
||||
```csharp
|
||||
_api.RegisterCommand("css_mycommand", "Description", callback);
|
||||
_api.UnRegisterCommand("css_mycommand");
|
||||
_api.GetTarget(command); // Parse player targets
|
||||
```
|
||||
|
||||
**[Learn more →](api/commands)**
|
||||
|
||||
---
|
||||
|
||||
### Menu System
|
||||
|
||||
Create interactive menus with automatic back button handling:
|
||||
|
||||
```csharp
|
||||
// Register category
|
||||
_api.RegisterMenuCategory("mycategory", "My Category", "@css/generic");
|
||||
|
||||
// Register menu
|
||||
_api.RegisterMenu("mycategory", "mymenu", "My Menu", CreateMenu, "@css/generic");
|
||||
|
||||
// Create menu with players
|
||||
_api.CreateMenuWithPlayers(context, admin, filter, onSelect);
|
||||
```
|
||||
|
||||
**[Learn more →](api/menus)**
|
||||
|
||||
---
|
||||
|
||||
### Penalty System
|
||||
|
||||
Issue and manage player penalties:
|
||||
|
||||
```csharp
|
||||
// Ban player
|
||||
_api.IssuePenalty(player, admin, PenaltyType.Ban, "Reason", 1440);
|
||||
|
||||
// Offline ban
|
||||
_api.IssuePenalty(steamId, admin, PenaltyType.Ban, "Reason", 0);
|
||||
|
||||
// Check penalties
|
||||
var status = _api.GetPlayerMuteStatus(player);
|
||||
```
|
||||
|
||||
**[Learn more →](api/penalties)**
|
||||
|
||||
---
|
||||
|
||||
### Event System
|
||||
|
||||
React to plugin events:
|
||||
|
||||
```csharp
|
||||
_api.OnSimpleAdminReady += () => { /* Plugin ready */ };
|
||||
_api.OnPlayerPenaltied += (player, admin, type, reason, duration, id, serverId) =>
|
||||
{
|
||||
// Player received penalty
|
||||
};
|
||||
```
|
||||
|
||||
**[Learn more →](api/events)**
|
||||
|
||||
---
|
||||
|
||||
### Utility Functions
|
||||
|
||||
Helper functions for common tasks:
|
||||
|
||||
```csharp
|
||||
// Get player info
|
||||
var playerInfo = _api.GetPlayerInfo(player);
|
||||
|
||||
// Get valid players
|
||||
var players = _api.GetValidPlayers();
|
||||
|
||||
// Check admin status
|
||||
if (_api.IsAdminSilent(admin)) { /* ... */ }
|
||||
|
||||
// Show admin activity
|
||||
_api.ShowAdminActivity("message_key", callerName, false, args);
|
||||
```
|
||||
|
||||
**[Learn more →](api/utilities)**
|
||||
|
||||
---
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Simple Command
|
||||
|
||||
```csharp
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var target in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
|
||||
{
|
||||
// Do something with target
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
### Simple Menu
|
||||
|
||||
```csharp
|
||||
private object CreateMyMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context,
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) => DoAction(admin, target)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Error Handling
|
||||
|
||||
```csharp
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("API not available!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.IsValid || !player.PawnIsAlive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### Resource Cleanup
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Unregister commands
|
||||
_api.UnRegisterCommand("css_mycommand");
|
||||
|
||||
// Unregister menus
|
||||
_api.UnregisterMenu("mycategory", "mymenu");
|
||||
|
||||
// Unsubscribe events
|
||||
_api.OnSimpleAdminReady -= OnReady;
|
||||
}
|
||||
```
|
||||
|
||||
### Translations
|
||||
|
||||
```csharp
|
||||
// Use per-player language support
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"translation_key",
|
||||
caller?.PlayerName,
|
||||
false,
|
||||
args
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference Implementation
|
||||
|
||||
The **Fun Commands Module** serves as a complete reference implementation demonstrating all API features:
|
||||
|
||||
- Command registration from config
|
||||
- Menu creation with context
|
||||
- Per-player translations
|
||||
- Proper cleanup
|
||||
- Code organization
|
||||
|
||||
**[View Source Code](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)**
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
CS2-SimpleAdmin follows a layered architecture:
|
||||
|
||||
**Layers:**
|
||||
1. **CounterStrikeSharp Integration** - Game event handling
|
||||
2. **Manager Layer** - Business logic (Bans, Mutes, Permissions)
|
||||
3. **Database Layer** - MySQL/SQLite with migrations
|
||||
4. **Menu System** - MenuManager with factory pattern
|
||||
5. **Command System** - Dynamic registration
|
||||
6. **Public API** - ICS2_SimpleAdminApi interface
|
||||
|
||||
**[Learn more →](architecture)**
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
### Ways to Contribute
|
||||
|
||||
1. **Report Bugs** - [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
|
||||
2. **Suggest Features** - [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
||||
3. **Submit Pull Requests** - Code contributions
|
||||
4. **Create Modules** - Extend functionality
|
||||
5. **Improve Documentation** - Help others learn
|
||||
|
||||
### Development Workflow
|
||||
|
||||
1. Fork the repository
|
||||
2. Create feature branch
|
||||
3. Make changes
|
||||
4. Test thoroughly
|
||||
5. Submit pull request
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
### Documentation
|
||||
|
||||
- **[API Reference](api/overview)** - Complete API documentation
|
||||
- **[Module Development](module/getting-started)** - Create modules
|
||||
- **[Architecture](architecture)** - Plugin design
|
||||
|
||||
### External Resources
|
||||
|
||||
- **[CounterStrikeSharp Docs](https://docs.cssharp.dev/)** - CSS framework
|
||||
- **[CS2 Docs](https://developer.valvesoftware.com/wiki/Counter-Strike_2)** - Game documentation
|
||||
|
||||
### Community
|
||||
|
||||
- **[GitHub](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
|
||||
- **[Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)** - Bug reports
|
||||
- **[Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)** - Questions and ideas
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
### Getting Help
|
||||
|
||||
1. **Check Documentation** - Most questions answered here
|
||||
2. **Search Issues** - Someone may have had same problem
|
||||
3. **Ask in Discussions** - Community help
|
||||
4. **Create Issue** - For bugs or feature requests
|
||||
|
||||
### Reporting Bugs
|
||||
|
||||
Include:
|
||||
- CS2-SimpleAdmin version
|
||||
- CounterStrikeSharp version
|
||||
- Error messages
|
||||
- Steps to reproduce
|
||||
- Expected vs actual behavior
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### For New Developers
|
||||
|
||||
1. **[Read API Overview](api/overview)** - Understand available features
|
||||
2. **[Study Examples](module/examples)** - Learn from code
|
||||
3. **[Create First Module](module/getting-started)** - Get hands-on
|
||||
|
||||
### For Advanced Developers
|
||||
|
||||
1. **[Read Architecture](architecture)** - Deep dive into structure
|
||||
2. **[Review Source Code](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Understand implementation
|
||||
3. **[Contribute](https://github.com/daffyyyy/CS2-SimpleAdmin/pulls)** - Help improve the plugin
|
||||
540
CS2-SimpleAdmin-docs/docs/developer/module/best-practices.md
Normal file
@@ -0,0 +1,540 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Best Practices
|
||||
|
||||
Guidelines for writing high-quality CS2-SimpleAdmin modules.
|
||||
|
||||
## Code Organization
|
||||
|
||||
### Use Partial Classes
|
||||
|
||||
Split your code into logical files:
|
||||
|
||||
```
|
||||
MyModule/
|
||||
├── MyModule.cs # Main class, initialization
|
||||
├── Commands.cs # Command handlers
|
||||
├── Menus.cs # Menu creation
|
||||
├── Actions.cs # Core logic
|
||||
└── Config.cs # Configuration
|
||||
```
|
||||
|
||||
```csharp
|
||||
// MyModule.cs
|
||||
public partial class MyModule : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
// Initialization
|
||||
}
|
||||
|
||||
// Commands.cs
|
||||
public partial class MyModule
|
||||
{
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Command logic
|
||||
}
|
||||
}
|
||||
|
||||
// Menus.cs
|
||||
public partial class MyModule
|
||||
{
|
||||
private object CreateMyMenu(CCSPlayerController player, MenuContext context)
|
||||
{
|
||||
// Menu logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Easy to navigate
|
||||
- ✅ Logical separation
|
||||
- ✅ Better maintainability
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Use Command Lists
|
||||
|
||||
Allow users to customize aliases:
|
||||
|
||||
```csharp
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
// ✅ Good - List allows multiple aliases
|
||||
public List<string> MyCommands { get; set; } = ["css_mycommand"];
|
||||
|
||||
// ❌ Bad - Single string
|
||||
public string MyCommand { get; set; } = "css_mycommand";
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```csharp
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api!.RegisterCommand(cmd, "Description", OnMyCommand);
|
||||
}
|
||||
```
|
||||
|
||||
### Provide Sensible Defaults
|
||||
|
||||
```csharp
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
// Good defaults
|
||||
public bool EnableFeature { get; set; } = true;
|
||||
public int MaxValue { get; set; } = 100;
|
||||
public List<string> Commands { get; set; } = ["css_default"];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Usage
|
||||
|
||||
### Always Check for Null
|
||||
|
||||
```csharp
|
||||
// ✅ Good
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("API not available!");
|
||||
return;
|
||||
}
|
||||
|
||||
_api.RegisterCommand(...);
|
||||
|
||||
// ❌ Bad
|
||||
_api!.RegisterCommand(...); // Can crash if null
|
||||
```
|
||||
|
||||
### Use OnSimpleAdminReady Pattern
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Handles both normal load and hot reload
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // Also call directly
|
||||
|
||||
// ❌ Bad - Only works on normal load
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
```
|
||||
|
||||
### Always Clean Up
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Unregister ALL commands
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api.UnRegisterCommand(cmd);
|
||||
}
|
||||
|
||||
// Unregister ALL menus
|
||||
_api.UnregisterMenu("category", "menu");
|
||||
|
||||
// Unsubscribe ALL events
|
||||
_api.OnSimpleAdminReady -= RegisterMenus;
|
||||
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Player Validation
|
||||
|
||||
### Validate Before Acting
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Multiple checks
|
||||
if (!player.IsValid)
|
||||
{
|
||||
Logger.LogWarning("Player is invalid!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.PawnIsAlive)
|
||||
{
|
||||
caller?.PrintToChat("Target must be alive!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (admin != null && !admin.CanTarget(player))
|
||||
{
|
||||
admin.PrintToChat("Cannot target this player!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Safe to proceed
|
||||
DoAction(player);
|
||||
```
|
||||
|
||||
### Check State Changes
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Validate in callback
|
||||
_api.AddMenuOption(menu, "Action", _ =>
|
||||
{
|
||||
// Validate again - player state may have changed
|
||||
if (!target.IsValid || !target.PawnIsAlive)
|
||||
return;
|
||||
|
||||
DoAction(target);
|
||||
});
|
||||
|
||||
// ❌ Bad - No validation in callback
|
||||
_api.AddMenuOption(menu, "Action", _ =>
|
||||
{
|
||||
DoAction(target); // Might crash!
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Translations
|
||||
|
||||
### Use MenuContext for Menus
|
||||
|
||||
```csharp
|
||||
// ✅ Good - No duplication
|
||||
_api.RegisterMenu("cat", "id", "Title", CreateMenu, "@css/generic");
|
||||
|
||||
private object CreateMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers(context, admin, filter, action);
|
||||
}
|
||||
|
||||
// ❌ Bad - Duplicates title and category
|
||||
_api.RegisterMenu("cat", "id", "Title", CreateMenuOld, "@css/generic");
|
||||
|
||||
private object CreateMenuOld(CCSPlayerController admin)
|
||||
{
|
||||
return _api.CreateMenuWithPlayers("Title", "cat", admin, filter, action);
|
||||
}
|
||||
```
|
||||
|
||||
### Use Per-Player Translations
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Each player sees their language
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"translation_key",
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
args
|
||||
);
|
||||
}
|
||||
|
||||
// ❌ Bad - Single language for all
|
||||
Server.PrintToChatAll($"{admin?.PlayerName} did something");
|
||||
```
|
||||
|
||||
### Provide English Fallbacks
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Fallback if translation missing
|
||||
_api.RegisterMenuCategory(
|
||||
"mycat",
|
||||
Localizer?["category_name"] ?? "Default Category Name",
|
||||
"@css/generic"
|
||||
);
|
||||
|
||||
// ❌ Bad - No fallback
|
||||
_api.RegisterMenuCategory(
|
||||
"mycat",
|
||||
Localizer["category_name"], // Crashes if no translation!
|
||||
"@css/generic"
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance
|
||||
|
||||
### Cache Expensive Operations
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Cache on first access
|
||||
private static Dictionary<int, string>? _itemCache;
|
||||
|
||||
private static Dictionary<int, string> GetItemCache()
|
||||
{
|
||||
if (_itemCache != null) return _itemCache;
|
||||
|
||||
// Build cache once
|
||||
_itemCache = new Dictionary<int, string>();
|
||||
// ... populate
|
||||
return _itemCache;
|
||||
}
|
||||
|
||||
// ❌ Bad - Rebuild every time
|
||||
private Dictionary<int, string> GetItems()
|
||||
{
|
||||
var items = new Dictionary<int, string>();
|
||||
// ... expensive operation
|
||||
return items;
|
||||
}
|
||||
```
|
||||
|
||||
### Efficient LINQ Queries
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Single query
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => p.IsValid && !p.IsBot && p.PawnIsAlive)
|
||||
.ToList();
|
||||
|
||||
// ❌ Bad - Multiple iterations
|
||||
var players = _api.GetValidPlayers();
|
||||
players = players.Where(p => p.IsValid).ToList();
|
||||
players = players.Where(p => !p.IsBot).ToList();
|
||||
players = players.Where(p => p.PawnIsAlive).ToList();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Log Errors
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Detailed logging
|
||||
try
|
||||
{
|
||||
DoAction();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to perform action: {ex.Message}");
|
||||
Logger.LogError($"Stack trace: {ex.StackTrace}");
|
||||
}
|
||||
|
||||
// ❌ Bad - Silent failure
|
||||
try
|
||||
{
|
||||
DoAction();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
```
|
||||
|
||||
### Graceful Degradation
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Continue with reduced functionality
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("SimpleAdmin API not found - limited functionality!");
|
||||
// Module still loads, just without SimpleAdmin integration
|
||||
return;
|
||||
}
|
||||
|
||||
// ❌ Bad - Crash the entire module
|
||||
_api = _pluginCapability.Get() ?? throw new Exception("No API!");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
### Validate Admin Permissions
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Check permissions
|
||||
[RequiresPermissions("@css/ban")]
|
||||
private void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Already validated by attribute
|
||||
}
|
||||
|
||||
// ❌ Bad - No permission check
|
||||
private void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Anyone can use this!
|
||||
}
|
||||
```
|
||||
|
||||
### Check Immunity
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Check immunity
|
||||
if (admin != null && !admin.CanTarget(target))
|
||||
{
|
||||
admin.PrintToChat($"Cannot target {target.PlayerName}!");
|
||||
return;
|
||||
}
|
||||
|
||||
// ❌ Bad - Ignore immunity
|
||||
DoAction(target); // Can target higher immunity!
|
||||
```
|
||||
|
||||
### Sanitize Input
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Validate and sanitize
|
||||
private void OnSetValueCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
if (!int.TryParse(command.GetArg(1), out int value))
|
||||
{
|
||||
caller?.PrintToChat("Invalid number!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value < 0 || value > 1000)
|
||||
{
|
||||
caller?.PrintToChat("Value must be between 0 and 1000!");
|
||||
return;
|
||||
}
|
||||
|
||||
SetValue(value);
|
||||
}
|
||||
|
||||
// ❌ Bad - No validation
|
||||
private void OnSetValueCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var value = int.Parse(command.GetArg(1)); // Can crash!
|
||||
SetValue(value); // No range check!
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
### Comment Complex Logic
|
||||
|
||||
```csharp
|
||||
// ✅ Good - Explain why, not what
|
||||
// We need to check immunity twice because player state can change
|
||||
// between menu creation and action execution
|
||||
if (!admin.CanTarget(player))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ❌ Bad - States the obvious
|
||||
// Check if admin can target player
|
||||
if (!admin.CanTarget(player))
|
||||
{
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### XML Documentation
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// Toggles god mode for the specified player.
|
||||
/// </summary>
|
||||
/// <param name="admin">Admin performing the action (null for console)</param>
|
||||
/// <param name="target">Player to toggle god mode for</param>
|
||||
/// <returns>True if god mode is now enabled, false otherwise</returns>
|
||||
public bool ToggleGodMode(CCSPlayerController? admin, CCSPlayerController target)
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Edge Cases
|
||||
|
||||
```csharp
|
||||
// Test with:
|
||||
// - Invalid players
|
||||
// - Disconnected players
|
||||
// - Players who changed teams
|
||||
// - Null admins (console)
|
||||
// - Silent admins
|
||||
// - Players with higher immunity
|
||||
```
|
||||
|
||||
### Test Hot Reload
|
||||
|
||||
```bash
|
||||
# Server console
|
||||
css_plugins reload YourModule
|
||||
```
|
||||
|
||||
Make sure everything works after reload!
|
||||
|
||||
---
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### ❌ Forgetting to Unsubscribe
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
// Missing unsubscribe = memory leak!
|
||||
// _api.OnSimpleAdminReady -= RegisterMenus; ← FORGOT THIS
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Not Checking API Availability
|
||||
|
||||
```csharp
|
||||
// Crashes if SimpleAdmin not loaded!
|
||||
_api.RegisterCommand(...); // ← No null check
|
||||
```
|
||||
|
||||
### ❌ Hardcoding Strings
|
||||
|
||||
```csharp
|
||||
// Bad - not translatable
|
||||
player.PrintToChat("You have been banned!");
|
||||
|
||||
// Good - uses translations
|
||||
var message = Localizer?["ban_message"] ?? "You have been banned!";
|
||||
player.PrintToChat(message);
|
||||
```
|
||||
|
||||
### ❌ Blocking Game Thread
|
||||
|
||||
```csharp
|
||||
// Bad - blocks game thread
|
||||
Thread.Sleep(5000);
|
||||
|
||||
// Good - use CounterStrikeSharp timers
|
||||
AddTimer(5.0f, () => DoAction());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference Implementation
|
||||
|
||||
Study the **Fun Commands Module** for best practices:
|
||||
|
||||
**[View Source](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)**
|
||||
|
||||
Shows:
|
||||
- ✅ Proper code organization
|
||||
- ✅ Configuration best practices
|
||||
- ✅ Menu creation with context
|
||||
- ✅ Per-player translations
|
||||
- ✅ Proper cleanup
|
||||
- ✅ Error handling
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Examples](examples)** - More code examples
|
||||
- **[API Reference](../api/overview)** - Full API documentation
|
||||
- **[GitHub](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Browse source code
|
||||
552
CS2-SimpleAdmin-docs/docs/developer/module/examples.md
Normal file
@@ -0,0 +1,552 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Code Examples
|
||||
|
||||
Practical examples for common module development scenarios.
|
||||
|
||||
## Complete Mini Module
|
||||
|
||||
A fully working minimal module:
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
namespace HelloModule;
|
||||
|
||||
public class HelloModule : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
public override string ModuleName => "Hello Module";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
public Config Config { get; set; } = new();
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Register command
|
||||
foreach (var cmd in Config.HelloCommands)
|
||||
{
|
||||
_api.RegisterCommand(cmd, "Say hello to a player", OnHelloCommand);
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnHelloCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
|
||||
{
|
||||
player.PrintToChat($"Hello {player.PlayerName}!");
|
||||
caller?.PrintToChat($"Said hello to {player.PlayerName}");
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
public void OnConfigParsed(Config config) => Config = config;
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
foreach (var cmd in Config.HelloCommands)
|
||||
{
|
||||
_api.UnRegisterCommand(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
public int Version { get; set; } = 1;
|
||||
public List<string> HelloCommands { get; set; } = ["css_hello"];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Command Examples
|
||||
|
||||
### Simple Target Command
|
||||
|
||||
```csharp
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/slay")]
|
||||
private void OnSlayCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
|
||||
{
|
||||
if (player.PawnIsAlive)
|
||||
{
|
||||
player.PlayerPawn?.Value?.CommitSuicide(false, true);
|
||||
caller?.PrintToChat($"Slayed {player.PlayerName}");
|
||||
}
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
### Command with Value Parameter
|
||||
|
||||
```csharp
|
||||
[CommandHelper(2, "<#userid or name> <value>")]
|
||||
[RequiresPermissions("@css/slay")]
|
||||
private void OnSetHpCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Parse HP value
|
||||
if (!int.TryParse(command.GetArg(2), out int hp) || hp < 1 || hp > 999)
|
||||
{
|
||||
caller?.PrintToChat("Invalid HP! Use 1-999");
|
||||
return;
|
||||
}
|
||||
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid && p.PawnIsAlive && caller!.CanTarget(p)))
|
||||
{
|
||||
player.PlayerPawn?.Value?.SetHealth(hp);
|
||||
caller?.PrintToChat($"Set {player.PlayerName} HP to {hp}");
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, $"css_sethp {hp}");
|
||||
}
|
||||
```
|
||||
|
||||
### Command with Penalty
|
||||
|
||||
```csharp
|
||||
[CommandHelper(1, "<#userid or name> [duration] [reason]")]
|
||||
[RequiresPermissions("@css/ban")]
|
||||
private void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
// Parse duration (default: 60 minutes)
|
||||
int duration = 60;
|
||||
if (command.ArgCount > 2)
|
||||
{
|
||||
int.TryParse(command.GetArg(2), out duration);
|
||||
}
|
||||
|
||||
// Get reason (default: "Banned")
|
||||
string reason = command.ArgCount > 3
|
||||
? string.Join(" ", command.ArgString.Split(' ').Skip(2))
|
||||
: "Banned";
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
|
||||
{
|
||||
_api.IssuePenalty(player, caller, PenaltyType.Ban, reason, duration);
|
||||
caller?.PrintToChat($"Banned {player.PlayerName} for {duration} minutes");
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Menu Examples
|
||||
|
||||
### Simple Player Selection Menu
|
||||
|
||||
```csharp
|
||||
private void RegisterMenus()
|
||||
{
|
||||
_api!.RegisterMenuCategory("actions", "Player Actions", "@css/generic");
|
||||
|
||||
_api.RegisterMenu(
|
||||
"actions",
|
||||
"kick",
|
||||
"Kick Player",
|
||||
CreateKickMenu,
|
||||
"@css/kick"
|
||||
);
|
||||
}
|
||||
|
||||
private object CreateKickMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context,
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) =>
|
||||
{
|
||||
Server.ExecuteCommand($"css_kick #{target.UserId} Kicked via menu");
|
||||
admin.PrintToChat($"Kicked {target.PlayerName}");
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Nested Menu (Player → Action)
|
||||
|
||||
```csharp
|
||||
private object CreatePlayerActionsMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
foreach (var player in _api.GetValidPlayers().Where(p => admin.CanTarget(p)))
|
||||
{
|
||||
_api.AddSubMenu(menu, player.PlayerName, admin =>
|
||||
{
|
||||
return CreateActionSelectMenu(admin, player);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateActionSelectMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack($"Actions: {target.PlayerName}", "actions", admin);
|
||||
|
||||
_api.AddMenuOption(menu, "Slay", _ =>
|
||||
{
|
||||
if (target.IsValid && target.PawnIsAlive)
|
||||
{
|
||||
target.PlayerPawn?.Value?.CommitSuicide(false, true);
|
||||
admin.PrintToChat($"Slayed {target.PlayerName}");
|
||||
}
|
||||
});
|
||||
|
||||
_api.AddMenuOption(menu, "Kick", _ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
Server.ExecuteCommand($"css_kick #{target.UserId}");
|
||||
}
|
||||
});
|
||||
|
||||
_api.AddMenuOption(menu, "Ban", _ =>
|
||||
{
|
||||
if (target.IsValid)
|
||||
{
|
||||
_api.IssuePenalty(target, admin, PenaltyType.Ban, "Banned via menu", 1440);
|
||||
}
|
||||
});
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
### Menu with Value Selection
|
||||
|
||||
```csharp
|
||||
private object CreateSetHpMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => p.PawnIsAlive && admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
_api.AddSubMenu(menu, player.PlayerName, admin =>
|
||||
{
|
||||
return CreateHpValueMenu(admin, player);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateHpValueMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack($"Set HP: {target.PlayerName}", "actions", admin);
|
||||
|
||||
var hpValues = new[] { 1, 10, 50, 100, 200, 500 };
|
||||
|
||||
foreach (var hp in hpValues)
|
||||
{
|
||||
_api.AddMenuOption(menu, $"{hp} HP", _ =>
|
||||
{
|
||||
if (target.IsValid && target.PawnIsAlive)
|
||||
{
|
||||
target.PlayerPawn?.Value?.SetHealth(hp);
|
||||
admin.PrintToChat($"Set {target.PlayerName} HP to {hp}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Event Examples
|
||||
|
||||
### React to Bans
|
||||
|
||||
```csharp
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null) return;
|
||||
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
if (type != PenaltyType.Ban) return;
|
||||
|
||||
var adminName = admin?.PlayerName ?? "Console";
|
||||
Logger.LogInformation($"Ban: {adminName} -> {player.PlayerName} ({duration}m): {reason}");
|
||||
|
||||
// Log to file
|
||||
File.AppendAllText("bans.log",
|
||||
$"[{DateTime.Now}] {player.PlayerName} banned by {adminName} for {duration}m: {reason}\n");
|
||||
}
|
||||
```
|
||||
|
||||
### Warning Escalation
|
||||
|
||||
```csharp
|
||||
private void OnPlayerPenaltied(
|
||||
PlayerInfo player,
|
||||
PlayerInfo? admin,
|
||||
PenaltyType type,
|
||||
string reason,
|
||||
int duration,
|
||||
int? penaltyId,
|
||||
int? serverId)
|
||||
{
|
||||
if (type != PenaltyType.Warn) return;
|
||||
|
||||
Logger.LogInformation($"{player.PlayerName} has {player.Warnings} warnings");
|
||||
|
||||
// Auto-ban at 3 warnings
|
||||
if (player.Warnings >= 3)
|
||||
{
|
||||
var controller = Utilities.GetPlayers()
|
||||
.FirstOrDefault(p => p.SteamID == player.SteamId);
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
_api!.IssuePenalty(
|
||||
controller,
|
||||
null,
|
||||
PenaltyType.Ban,
|
||||
"Automatic: 3 warnings",
|
||||
1440 // 1 day
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Translation Examples
|
||||
|
||||
### Module with Translations
|
||||
|
||||
**lang/en.json:**
|
||||
```json
|
||||
{
|
||||
"category_name": "My Module",
|
||||
"menu_name": "My Action",
|
||||
"action_message": "{lightred}{0}{default} performed action on {lightred}{1}{default}!",
|
||||
"error_invalid_player": "{red}Error:{default} Invalid player!",
|
||||
"success": "{green}Success!{default} Action completed."
|
||||
}
|
||||
```
|
||||
|
||||
**Code:**
|
||||
```csharp
|
||||
private void PerformAction(CCSPlayerController? admin, CCSPlayerController target)
|
||||
{
|
||||
// Perform action
|
||||
DoSomething(target);
|
||||
|
||||
// Show activity with translation
|
||||
if (admin == null || !_api!.IsAdminSilent(admin))
|
||||
{
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"action_message",
|
||||
admin?.PlayerName,
|
||||
false,
|
||||
admin?.PlayerName ?? "Console",
|
||||
target.PlayerName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Send success message
|
||||
admin?.PrintToChat(Localizer?["success"] ?? "Success!");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Utility Examples
|
||||
|
||||
### Get Players by Team
|
||||
|
||||
```csharp
|
||||
private List<CCSPlayerController> GetTeamPlayers(CsTeam team)
|
||||
{
|
||||
return _api!.GetValidPlayers()
|
||||
.Where(p => p.Team == team)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Usage
|
||||
var ctPlayers = GetTeamPlayers(CsTeam.CounterTerrorist);
|
||||
var tPlayers = GetTeamPlayers(CsTeam.Terrorist);
|
||||
```
|
||||
|
||||
### Get Alive Players
|
||||
|
||||
```csharp
|
||||
private List<CCSPlayerController> GetAlivePlayers()
|
||||
{
|
||||
return _api!.GetValidPlayers()
|
||||
.Where(p => p.PawnIsAlive)
|
||||
.ToList();
|
||||
}
|
||||
```
|
||||
|
||||
### Notify Admins
|
||||
|
||||
```csharp
|
||||
private void NotifyAdmins(string message, string permission = "@css/generic")
|
||||
{
|
||||
var admins = _api!.GetValidPlayers()
|
||||
.Where(p => AdminManager.PlayerHasPermissions(p, permission));
|
||||
|
||||
foreach (var admin in admins)
|
||||
{
|
||||
admin.PrintToChat(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
NotifyAdmins("⚠ Important admin message", "@css/root");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Timer Examples
|
||||
|
||||
### Delayed Action
|
||||
|
||||
```csharp
|
||||
private void DelayedAction(CCSPlayerController player, float delay)
|
||||
{
|
||||
AddTimer(delay, () =>
|
||||
{
|
||||
if (player.IsValid && player.PawnIsAlive)
|
||||
{
|
||||
DoAction(player);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Repeating Timer
|
||||
|
||||
```csharp
|
||||
private void StartRepeatingAction()
|
||||
{
|
||||
AddTimer(1.0f, () =>
|
||||
{
|
||||
foreach (var player in _api!.GetValidPlayers())
|
||||
{
|
||||
if (player.PawnIsAlive)
|
||||
{
|
||||
UpdatePlayer(player);
|
||||
}
|
||||
}
|
||||
}, TimerFlags.REPEAT);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Multiple Feature Toggles
|
||||
|
||||
```csharp
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
[JsonPropertyName("EnableFeature1")]
|
||||
public bool EnableFeature1 { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("EnableFeature2")]
|
||||
public bool EnableFeature2 { get; set; } = false;
|
||||
|
||||
[JsonPropertyName("Feature1Commands")]
|
||||
public List<string> Feature1Commands { get; set; } = ["css_feature1"];
|
||||
|
||||
[JsonPropertyName("Feature2Commands")]
|
||||
public List<string> Feature2Commands { get; set; } = ["css_feature2"];
|
||||
|
||||
[JsonPropertyName("MaxValue")]
|
||||
public int MaxValue { get; set; } = 100;
|
||||
}
|
||||
|
||||
// Usage
|
||||
private void RegisterCommands()
|
||||
{
|
||||
if (Config.EnableFeature1)
|
||||
{
|
||||
foreach (var cmd in Config.Feature1Commands)
|
||||
{
|
||||
_api!.RegisterCommand(cmd, "Feature 1", OnFeature1Command);
|
||||
}
|
||||
}
|
||||
|
||||
if (Config.EnableFeature2)
|
||||
{
|
||||
foreach (var cmd in Config.Feature2Commands)
|
||||
{
|
||||
_api!.RegisterCommand(cmd, "Feature 2", OnFeature2Command);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Best Practices](best-practices)** - Write better code
|
||||
- **[Getting Started](getting-started)** - Create your first module
|
||||
- **[API Reference](../api/overview)** - Full API documentation
|
||||
- **[Fun Commands Source](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** - Complete reference implementation
|
||||
282
CS2-SimpleAdmin-docs/docs/developer/module/getting-started.md
Normal file
@@ -0,0 +1,282 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Getting Started with Module Development
|
||||
|
||||
Step-by-step guide to creating your first CS2-SimpleAdmin module.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin:
|
||||
|
||||
- C# knowledge (intermediate level)
|
||||
- .NET 8.0 SDK installed
|
||||
- Visual Studio 2022 or VS Code
|
||||
- Basic understanding of CounterStrikeSharp
|
||||
- CS2 dedicated server for testing
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Project
|
||||
|
||||
### Using .NET CLI
|
||||
|
||||
```bash
|
||||
dotnet new classlib -n MyModule -f net8.0
|
||||
cd MyModule
|
||||
```
|
||||
|
||||
### Using Visual Studio
|
||||
|
||||
1. File → New → Project
|
||||
2. Select "Class Library (.NET 8.0)"
|
||||
3. Name: `MyModule`
|
||||
4. Click Create
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Add References
|
||||
|
||||
Edit `MyModule.csproj`:
|
||||
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="CounterStrikeSharp.API">
|
||||
<HintPath>path/to/CounterStrikeSharp.API.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
|
||||
<Reference Include="CS2-SimpleAdminApi">
|
||||
<HintPath>path/to/CS2-SimpleAdminApi.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Create Main Plugin Class
|
||||
|
||||
Create `MyModule.cs`:
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
namespace MyModule;
|
||||
|
||||
public class MyModule : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
public override string ModuleName => "My Module";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
public override string ModuleAuthor => "Your Name";
|
||||
public override string ModuleDescription => "My awesome module";
|
||||
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability =
|
||||
new("simpleadmin:api");
|
||||
|
||||
public Config Config { get; set; } = new();
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
// Get SimpleAdmin API
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInformation("MyModule loaded successfully!");
|
||||
|
||||
// Register features
|
||||
RegisterCommands();
|
||||
|
||||
// Register menus when ready
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // Also call for hot reload
|
||||
}
|
||||
|
||||
private void RegisterCommands()
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api.RegisterCommand(cmd, "My command description", OnMyCommand);
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterMenus()
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
_api.RegisterMenuCategory("mymodule", "My Module", "@css/generic");
|
||||
_api.RegisterMenu("mymodule", "mymenu", "My Menu", CreateMyMenu, "@css/generic");
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var player in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
|
||||
{
|
||||
player.PrintToChat($"Hello from MyModule!");
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
private object CreateMyMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context,
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) =>
|
||||
{
|
||||
target.PrintToChat("You were selected!");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void OnConfigParsed(Config config)
|
||||
{
|
||||
Config = config;
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
// Unregister commands
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api.UnRegisterCommand(cmd);
|
||||
}
|
||||
|
||||
// Unregister menus
|
||||
_api.UnregisterMenu("mymodule", "mymenu");
|
||||
|
||||
// Unsubscribe events
|
||||
_api.OnSimpleAdminReady -= RegisterMenus;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Create Configuration
|
||||
|
||||
Create `Config.cs`:
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
[JsonPropertyName("Version")]
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
[JsonPropertyName("MyCommands")]
|
||||
public List<string> MyCommands { get; set; } = ["css_mycommand"];
|
||||
|
||||
[JsonPropertyName("EnableFeature")]
|
||||
public bool EnableFeature { get; set; } = true;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Build and Deploy
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
dotnet build -c Release
|
||||
```
|
||||
|
||||
### Deploy
|
||||
|
||||
Copy files to server:
|
||||
```
|
||||
game/csgo/addons/counterstrikesharp/plugins/MyModule/
|
||||
└── MyModule.dll
|
||||
```
|
||||
|
||||
### Restart Server
|
||||
|
||||
```bash
|
||||
# Server console
|
||||
css_plugins reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Test
|
||||
|
||||
1. Join your server
|
||||
2. Open admin menu: `css_admin`
|
||||
3. Look for "My Module" category
|
||||
4. Test command: `css_mycommand @me`
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Best Practices](best-practices)** - Write better code
|
||||
- **[Examples](examples)** - More code examples
|
||||
- **[API Reference](../api/overview)** - Full API documentation
|
||||
- **[Fun Commands Module](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** - Reference implementation
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
### API Not Found
|
||||
|
||||
**Error:** `CS2-SimpleAdmin API not found!`
|
||||
|
||||
**Solution:**
|
||||
- Ensure CS2-SimpleAdmin is installed
|
||||
- Check that CS2-SimpleAdminApi.dll is in shared folder
|
||||
- Verify CS2-SimpleAdmin loads before your module
|
||||
|
||||
### Commands Not Working
|
||||
|
||||
**Check:**
|
||||
- Command registered in `RegisterCommands()`
|
||||
- Permission is correct
|
||||
- Player has required permission
|
||||
|
||||
### Menu Not Showing
|
||||
|
||||
**Check:**
|
||||
- `OnSimpleAdminReady` event subscribed
|
||||
- Menu registered in category
|
||||
- Permission is correct
|
||||
- SimpleAdmin loaded successfully
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **[Module Development Guide](../../modules/development)** - Detailed guide
|
||||
- **[GitHub Repository](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
|
||||
- **[CounterStrikeSharp Docs](https://docs.cssharp.dev/)** - CSS framework
|
||||
47
CS2-SimpleAdmin-docs/docs/intro.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Tutorial Intro
|
||||
|
||||
Let's discover **Docusaurus in less than 5 minutes**.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Get started by **creating a new site**.
|
||||
|
||||
Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**.
|
||||
|
||||
### What you'll need
|
||||
|
||||
- [Node.js](https://nodejs.org/en/download/) version 20.0 or above:
|
||||
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
|
||||
|
||||
## Generate a new site
|
||||
|
||||
Generate a new Docusaurus site using the **classic template**.
|
||||
|
||||
The classic template will automatically be added to your project after you run the command:
|
||||
|
||||
```bash
|
||||
npm init docusaurus@latest my-website classic
|
||||
```
|
||||
|
||||
You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
|
||||
|
||||
The command also installs all necessary dependencies you need to run Docusaurus.
|
||||
|
||||
## Start your site
|
||||
|
||||
Run the development server:
|
||||
|
||||
```bash
|
||||
cd my-website
|
||||
npm run start
|
||||
```
|
||||
|
||||
The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
|
||||
|
||||
The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
|
||||
|
||||
Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes.
|
||||
799
CS2-SimpleAdmin-docs/docs/modules/development.md
Normal file
@@ -0,0 +1,799 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Module Development
|
||||
|
||||
Learn how to create your own CS2-SimpleAdmin modules.
|
||||
|
||||
## Introduction
|
||||
|
||||
Creating modules for CS2-SimpleAdmin allows you to extend the plugin's functionality while keeping your code separate and maintainable.
|
||||
|
||||
:::tip Reference Implementation
|
||||
The **[Fun Commands Module](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** serves as a complete reference implementation. Study its code to learn best practices!
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Knowledge Required
|
||||
|
||||
- C# programming (intermediate level)
|
||||
- .NET 8.0
|
||||
- CounterStrikeSharp basics
|
||||
- Understanding of CS2-SimpleAdmin structure
|
||||
|
||||
### Tools Needed
|
||||
|
||||
- Visual Studio 2022 or VS Code
|
||||
- .NET 8.0 SDK
|
||||
- CS2 server for testing
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Create Project
|
||||
|
||||
```bash
|
||||
dotnet new classlib -n YourModuleName -f net8.0
|
||||
cd YourModuleName
|
||||
```
|
||||
|
||||
### 2. Add References
|
||||
|
||||
Edit your `.csproj` file:
|
||||
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- CounterStrikeSharp -->
|
||||
<Reference Include="CounterStrikeSharp.API">
|
||||
<HintPath>path/to/CounterStrikeSharp.API.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
|
||||
<!-- CS2-SimpleAdmin API -->
|
||||
<Reference Include="CS2-SimpleAdminApi">
|
||||
<HintPath>path/to/CS2-SimpleAdminApi.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
```
|
||||
|
||||
### 3. Create Main Plugin Class
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
namespace YourModuleName;
|
||||
|
||||
public class YourModule : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
public override string ModuleName => "Your Module Name";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
public override string ModuleAuthor => "Your Name";
|
||||
public override string ModuleDescription => "Description";
|
||||
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
public Config Config { get; set; } = new();
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
// Get SimpleAdmin API
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Register your commands and menus
|
||||
RegisterCommands();
|
||||
_api.OnSimpleAdminReady += RegisterMenus;
|
||||
RegisterMenus(); // Fallback for hot reload
|
||||
}
|
||||
|
||||
public void OnConfigParsed(Config config)
|
||||
{
|
||||
Config = config;
|
||||
}
|
||||
|
||||
private void RegisterCommands()
|
||||
{
|
||||
// Register commands here
|
||||
}
|
||||
|
||||
private void RegisterMenus()
|
||||
{
|
||||
// Register menus here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Structure
|
||||
|
||||
### Recommended File Organization
|
||||
|
||||
```
|
||||
YourModuleName/
|
||||
├── YourModule.cs # Main plugin class
|
||||
├── Config.cs # Configuration
|
||||
├── Commands.cs # Command handlers (partial class)
|
||||
├── Menus.cs # Menu creation (partial class)
|
||||
├── Actions.cs # Core logic (partial class)
|
||||
├── lang/ # Translations
|
||||
│ ├── en.json
|
||||
│ ├── pl.json
|
||||
│ └── ...
|
||||
└── YourModuleName.csproj
|
||||
```
|
||||
|
||||
### Using Partial Classes
|
||||
|
||||
Split your code for better organization:
|
||||
|
||||
```csharp
|
||||
// YourModule.cs
|
||||
public partial class YourModule : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
// Plugin initialization
|
||||
}
|
||||
|
||||
// Commands.cs
|
||||
public partial class YourModule
|
||||
{
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Command logic
|
||||
}
|
||||
}
|
||||
|
||||
// Menus.cs
|
||||
public partial class YourModule
|
||||
{
|
||||
private object CreateMyMenu(CCSPlayerController player, MenuContext context)
|
||||
{
|
||||
// Menu creation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Create Config Class
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
[JsonPropertyName("Version")]
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
[JsonPropertyName("MyCommands")]
|
||||
public List<string> MyCommands { get; set; } = ["css_mycommand"];
|
||||
|
||||
[JsonPropertyName("EnableFeature")]
|
||||
public bool EnableFeature { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("MaxValue")]
|
||||
public int MaxValue { get; set; } = 100;
|
||||
}
|
||||
```
|
||||
|
||||
### Config Best Practices
|
||||
|
||||
1. **Use command lists** - Allow users to add aliases or disable features
|
||||
2. **Provide defaults** - Sensible default values
|
||||
3. **Version your config** - Track config changes
|
||||
4. **Document settings** - Clear property names
|
||||
|
||||
---
|
||||
|
||||
## Registering Commands
|
||||
|
||||
### Basic Command Registration
|
||||
|
||||
```csharp
|
||||
private void RegisterCommands()
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api.RegisterCommand(cmd, "Command description", OnMyCommand);
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Get target players
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
// Filter for valid players
|
||||
var players = targets.Players
|
||||
.Where(p => p.IsValid && !p.IsBot)
|
||||
.ToList();
|
||||
|
||||
// Process each player
|
||||
foreach (var player in players)
|
||||
{
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
DoSomething(caller, player);
|
||||
}
|
||||
}
|
||||
|
||||
// Log the command
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
```
|
||||
|
||||
### Command Cleanup
|
||||
|
||||
Always unregister commands when unloading:
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
foreach (var cmd in Config.MyCommands)
|
||||
{
|
||||
_api.UnRegisterCommand(cmd);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Creating Menus
|
||||
|
||||
### Register Menu Category
|
||||
|
||||
```csharp
|
||||
private void RegisterMenus()
|
||||
{
|
||||
if (_api == null || _menusRegistered) return;
|
||||
|
||||
// Register category
|
||||
_api.RegisterMenuCategory(
|
||||
"mycategory",
|
||||
Localizer?["category_name"] ?? "My Category",
|
||||
"@css/generic"
|
||||
);
|
||||
|
||||
// Register menu
|
||||
_api.RegisterMenu(
|
||||
"mycategory",
|
||||
"mymenu",
|
||||
Localizer?["menu_name"] ?? "My Menu",
|
||||
CreateMyMenu,
|
||||
"@css/generic",
|
||||
"css_mycommand" // For permission override
|
||||
);
|
||||
|
||||
_menusRegistered = true;
|
||||
}
|
||||
```
|
||||
|
||||
### Menu with Player Selection (NEW API)
|
||||
|
||||
```csharp
|
||||
private object CreateMyMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
// Context contains: CategoryId, MenuId, MenuTitle, Permission, CommandName
|
||||
// No need to repeat "mycategory" and "My Menu" here!
|
||||
|
||||
return _api!.CreateMenuWithPlayers(
|
||||
context, // ← Automatically uses menu title and category
|
||||
admin,
|
||||
player => player.IsValid && admin.CanTarget(player),
|
||||
(admin, target) => DoSomethingToPlayer(admin, target)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Menu with Custom Options
|
||||
|
||||
```csharp
|
||||
private object CreateValueSelectionMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
var values = new[] { 10, 25, 50, 100, 200 };
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
_api.AddMenuOption(menu, $"{value} points", player =>
|
||||
{
|
||||
GivePoints(player, value);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
### Nested Menus
|
||||
|
||||
```csharp
|
||||
private object CreatePlayerSelectionMenu(CCSPlayerController admin, MenuContext context)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack(context, admin);
|
||||
|
||||
var players = _api.GetValidPlayers()
|
||||
.Where(p => admin.CanTarget(p));
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
_api.AddSubMenu(menu, player.PlayerName, admin =>
|
||||
{
|
||||
return CreateValueMenu(admin, player);
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private object CreateValueMenu(CCSPlayerController admin, CCSPlayerController target)
|
||||
{
|
||||
var menu = _api!.CreateMenuWithBack($"Select value for {target.PlayerName}", "mycategory", admin);
|
||||
|
||||
// Add options...
|
||||
|
||||
return menu;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Translations
|
||||
|
||||
### Create Translation Files
|
||||
|
||||
Create `lang/en.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"command_success": "{green}Success! {default}Action performed on {lightred}{0}",
|
||||
"command_failed": "{red}Failed! {default}Could not perform action",
|
||||
"menu_title": "My Custom Menu"
|
||||
}
|
||||
```
|
||||
|
||||
### Use Translations in Code
|
||||
|
||||
```csharp
|
||||
// In commands
|
||||
private void OnMyCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Using module's own localizer for per-player language
|
||||
if (Localizer != null)
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"command_success",
|
||||
caller?.PlayerName,
|
||||
false,
|
||||
target.PlayerName
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Language Support
|
||||
|
||||
Create files for each language:
|
||||
- `lang/en.json` - English
|
||||
- `lang/pl.json` - Polish
|
||||
- `lang/ru.json` - Russian
|
||||
- `lang/de.json` - German
|
||||
- etc.
|
||||
|
||||
---
|
||||
|
||||
## Working with API
|
||||
|
||||
### Issue Penalties
|
||||
|
||||
```csharp
|
||||
// Ban online player
|
||||
_api!.IssuePenalty(
|
||||
player,
|
||||
admin,
|
||||
PenaltyType.Ban,
|
||||
"Cheating",
|
||||
1440 // 1 day in minutes
|
||||
);
|
||||
|
||||
// Ban offline player by SteamID
|
||||
_api!.IssuePenalty(
|
||||
new SteamID(76561198012345678),
|
||||
admin,
|
||||
PenaltyType.Ban,
|
||||
"Ban evasion",
|
||||
0 // Permanent
|
||||
);
|
||||
|
||||
// Other penalty types
|
||||
_api!.IssuePenalty(player, admin, PenaltyType.Gag, "Chat spam", 30);
|
||||
_api!.IssuePenalty(player, admin, PenaltyType.Mute, "Mic spam", 60);
|
||||
_api!.IssuePenalty(player, admin, PenaltyType.Silence, "Total abuse", 120);
|
||||
_api!.IssuePenalty(player, admin, PenaltyType.Warn, "Rule break");
|
||||
```
|
||||
|
||||
### Get Player Information
|
||||
|
||||
```csharp
|
||||
// Get player info with penalty data
|
||||
var playerInfo = _api!.GetPlayerInfo(player);
|
||||
|
||||
Console.WriteLine($"Player: {playerInfo.PlayerName}");
|
||||
Console.WriteLine($"SteamID: {playerInfo.SteamId}");
|
||||
Console.WriteLine($"Warnings: {playerInfo.Warnings}");
|
||||
|
||||
// Get player mute status
|
||||
var muteStatus = _api!.GetPlayerMuteStatus(player);
|
||||
|
||||
if (muteStatus.ContainsKey(PenaltyType.Gag))
|
||||
{
|
||||
Console.WriteLine("Player is gagged");
|
||||
}
|
||||
```
|
||||
|
||||
### Check Admin Status
|
||||
|
||||
```csharp
|
||||
// Check if admin is in silent mode
|
||||
if (_api!.IsAdminSilent(admin))
|
||||
{
|
||||
// Don't broadcast this action
|
||||
}
|
||||
|
||||
// Get all silent admins
|
||||
var silentAdmins = _api!.ListSilentAdminsSlots();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Events
|
||||
|
||||
### Subscribe to Events
|
||||
|
||||
```csharp
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
|
||||
// Subscribe to events
|
||||
_api.OnSimpleAdminReady += OnSimpleAdminReady;
|
||||
_api.OnPlayerPenaltied += OnPlayerPenaltied;
|
||||
_api.OnPlayerPenaltiedAdded += OnPlayerPenaltiedAdded;
|
||||
_api.OnAdminShowActivity += OnAdminShowActivity;
|
||||
}
|
||||
|
||||
private void OnSimpleAdminReady()
|
||||
{
|
||||
Logger.LogInformation("SimpleAdmin is ready!");
|
||||
RegisterMenus();
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltied(PlayerInfo player, PlayerInfo? admin,
|
||||
PenaltyType type, string reason, int duration, int? penaltyId, int? serverId)
|
||||
{
|
||||
Logger.LogInformation($"{player.PlayerName} received {type} for {reason}");
|
||||
}
|
||||
|
||||
private void OnPlayerPenaltiedAdded(SteamID steamId, PlayerInfo? admin,
|
||||
PenaltyType type, string reason, int duration, int? penaltyId, int? serverId)
|
||||
{
|
||||
Logger.LogInformation($"Offline ban added to {steamId}");
|
||||
}
|
||||
|
||||
private void OnAdminShowActivity(string messageKey, string? callerName,
|
||||
bool dontPublish, object messageArgs)
|
||||
{
|
||||
// React to admin activity
|
||||
}
|
||||
```
|
||||
|
||||
### Unsubscribe on Unload
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
_api.OnSimpleAdminReady -= OnSimpleAdminReady;
|
||||
_api.OnPlayerPenaltied -= OnPlayerPenaltied;
|
||||
// ... unsubscribe all events
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Check for Null
|
||||
|
||||
```csharp
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("API not available!");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Validate Player State
|
||||
|
||||
```csharp
|
||||
if (!player.IsValid || !player.PawnIsAlive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Check Target Permissions
|
||||
|
||||
```csharp
|
||||
if (!caller.CanTarget(target))
|
||||
{
|
||||
// caller can't target this player (immunity)
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Log Commands
|
||||
|
||||
```csharp
|
||||
_api.LogCommand(caller, command);
|
||||
// or
|
||||
_api.LogCommand(caller, $"css_mycommand {player.PlayerName}");
|
||||
```
|
||||
|
||||
### 5. Use Per-Player Translations
|
||||
|
||||
```csharp
|
||||
// Each player sees message in their language!
|
||||
_api.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"translation_key",
|
||||
callerName,
|
||||
false,
|
||||
args
|
||||
);
|
||||
```
|
||||
|
||||
### 6. Clean Up Resources
|
||||
|
||||
```csharp
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
// Unregister commands
|
||||
// Unregister menus
|
||||
// Unsubscribe events
|
||||
// Dispose resources
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Player Targeting Helper
|
||||
|
||||
```csharp
|
||||
private List<CCSPlayerController> GetTargets(CommandInfo command, CCSPlayerController? caller)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return new List<CCSPlayerController>();
|
||||
|
||||
return targets.Players
|
||||
.Where(p => p.IsValid && !p.IsBot && caller!.CanTarget(p))
|
||||
.ToList();
|
||||
}
|
||||
```
|
||||
|
||||
### Menu Context Pattern (NEW!)
|
||||
|
||||
```csharp
|
||||
// ✅ NEW: Use context to avoid duplication
|
||||
private object CreateMenu(CCSPlayerController player, MenuContext context)
|
||||
{
|
||||
// context.MenuTitle, context.CategoryId already set!
|
||||
return _api!.CreateMenuWithPlayers(context, player, filter, action);
|
||||
}
|
||||
|
||||
// ❌ OLD: Had to repeat title and category
|
||||
private object CreateMenu(CCSPlayerController player)
|
||||
{
|
||||
return _api!.CreateMenuWithPlayers("My Menu", "mycategory", player, filter, action);
|
||||
}
|
||||
```
|
||||
|
||||
### Action with Activity Message
|
||||
|
||||
```csharp
|
||||
private void DoAction(CCSPlayerController? caller, CCSPlayerController target)
|
||||
{
|
||||
// Perform action
|
||||
// ...
|
||||
|
||||
// Show activity
|
||||
if (caller == null || !_api!.IsAdminSilent(caller))
|
||||
{
|
||||
_api!.ShowAdminActivityLocalized(
|
||||
Localizer,
|
||||
"action_message",
|
||||
caller?.PlayerName,
|
||||
false,
|
||||
target.PlayerName
|
||||
);
|
||||
}
|
||||
|
||||
// Log action
|
||||
_api!.LogCommand(caller, $"css_action {target.PlayerName}");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Module
|
||||
|
||||
### 1. Build
|
||||
|
||||
```bash
|
||||
dotnet build -c Release
|
||||
```
|
||||
|
||||
### 2. Copy to Server
|
||||
|
||||
```
|
||||
game/csgo/addons/counterstrikesharp/plugins/YourModuleName/
|
||||
```
|
||||
|
||||
### 3. Test
|
||||
|
||||
- Start server
|
||||
- Check console for load messages
|
||||
- Test commands
|
||||
- Test menus
|
||||
- Check translations
|
||||
|
||||
### 4. Debug
|
||||
|
||||
Enable detailed logging:
|
||||
```csharp
|
||||
Logger.LogInformation("Debug: ...");
|
||||
Logger.LogWarning("Warning: ...");
|
||||
Logger.LogError("Error: ...");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example: Complete Mini-Module
|
||||
|
||||
Here's a complete working example:
|
||||
|
||||
```csharp
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Capabilities;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
namespace ExampleModule;
|
||||
|
||||
public class ExampleModule : BasePlugin, IPluginConfig<Config>
|
||||
{
|
||||
public override string ModuleName => "Example Module";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
|
||||
private ICS2_SimpleAdminApi? _api;
|
||||
private readonly PluginCapability<ICS2_SimpleAdminApi> _pluginCapability = new("simpleadmin:api");
|
||||
|
||||
public Config Config { get; set; } = new();
|
||||
|
||||
public override void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
_api = _pluginCapability.Get();
|
||||
if (_api == null)
|
||||
{
|
||||
Logger.LogError("CS2-SimpleAdmin API not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Register command
|
||||
if (Config.ExampleCommands.Count > 0)
|
||||
{
|
||||
foreach (var cmd in Config.ExampleCommands)
|
||||
{
|
||||
_api.RegisterCommand(cmd, "Example command", OnExampleCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnConfigParsed(Config config)
|
||||
{
|
||||
Config = config;
|
||||
}
|
||||
|
||||
[CommandHelper(1, "<#userid or name>")]
|
||||
[RequiresPermissions("@css/generic")]
|
||||
private void OnExampleCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var targets = _api!.GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
foreach (var target in targets.Players.Where(p => p.IsValid && caller!.CanTarget(p)))
|
||||
{
|
||||
// Do something to target
|
||||
caller?.PrintToChat($"Performed action on {target.PlayerName}");
|
||||
}
|
||||
|
||||
_api.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
if (_api == null) return;
|
||||
|
||||
foreach (var cmd in Config.ExampleCommands)
|
||||
{
|
||||
_api.UnRegisterCommand(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Config : IBasePluginConfig
|
||||
{
|
||||
public int Version { get; set; } = 1;
|
||||
public List<string> ExampleCommands { get; set; } = ["css_example"];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Study Fun Commands Module](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** - Complete reference
|
||||
- **[Read API Documentation](../developer/api/overview)** - Full API reference
|
||||
- **[Check Examples](../developer/module/examples)** - More code examples
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **[CS2-SimpleAdmin GitHub](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code
|
||||
- **[CounterStrikeSharp Docs](https://docs.cssharp.dev/)** - CSS documentation
|
||||
- **[Module Development Guide](../developer/module/getting-started)** - Detailed guide
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
- **Issues:** [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
|
||||
- **Discussions:** [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
||||
- **Examples:** Study official modules for reference
|
||||
691
CS2-SimpleAdmin-docs/docs/modules/funcommands.md
Normal file
@@ -0,0 +1,691 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Fun Commands Module
|
||||
|
||||
Add entertaining and powerful player manipulation commands to your server.
|
||||
|
||||
## Overview
|
||||
|
||||
The Fun Commands module extends CS2-SimpleAdmin with commands for god mode, noclip, freeze, respawn, weapon management, and player attribute modification.
|
||||
|
||||
**Module Name:** `CS2-SimpleAdmin_FunCommands`
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- ⭐ God Mode - Make players invincible
|
||||
- 👻 No Clip - Allow players to fly through walls
|
||||
- 🧊 Freeze/Unfreeze - Freeze players in place
|
||||
- 🔄 Respawn - Bring dead players back
|
||||
- 🔫 Give Weapons - Provide any weapon to players
|
||||
- 🗑️ Strip Weapons - Remove all weapons
|
||||
- ❤️ Set HP - Modify player health
|
||||
- ⚡ Set Speed - Change movement speed
|
||||
- 🌙 Set Gravity - Modify gravity
|
||||
- 💰 Set Money - Adjust player money
|
||||
- 📏 Resize Player - Change player model size
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- CS2-SimpleAdmin installed and working
|
||||
- CS2-SimpleAdminApi.dll in shared folder
|
||||
|
||||
### Install Steps
|
||||
|
||||
1. **Download** the module from releases
|
||||
|
||||
2. **Extract** to your server:
|
||||
```
|
||||
game/csgo/addons/counterstrikesharp/plugins/CS2-SimpleAdmin_FunCommands/
|
||||
```
|
||||
|
||||
3. **Restart** your server or reload plugins:
|
||||
```
|
||||
css_plugins reload
|
||||
```
|
||||
|
||||
4. **Verify** the module loaded:
|
||||
- Check server console for load message
|
||||
- Try `css_admin` and look for "Fun Commands" menu
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### God Mode
|
||||
|
||||
Toggle god mode (invincibility) for a player.
|
||||
|
||||
```bash
|
||||
css_god <#userid or name>
|
||||
css_godmode <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cheats`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_god #123
|
||||
css_god PlayerName
|
||||
css_god @all # Toggle god mode for everyone
|
||||
```
|
||||
|
||||
**Effects:**
|
||||
- Player takes no damage
|
||||
- Toggles on/off with each use
|
||||
|
||||
---
|
||||
|
||||
### No Clip
|
||||
|
||||
Enable noclip mode (fly through walls).
|
||||
|
||||
```bash
|
||||
css_noclip <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cheats`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_noclip #123
|
||||
css_noclip PlayerName
|
||||
```
|
||||
|
||||
**Effects:**
|
||||
- Player can fly
|
||||
- Can pass through walls
|
||||
- Gravity disabled
|
||||
- Toggles on/off with each use
|
||||
|
||||
---
|
||||
|
||||
### Freeze
|
||||
|
||||
Freeze a player in place.
|
||||
|
||||
```bash
|
||||
css_freeze <#userid or name> [duration]
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `duration` - Freeze duration in seconds (optional, default: permanent until unfreeze)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_freeze #123 # Freeze permanently
|
||||
css_freeze PlayerName 30 # Freeze for 30 seconds
|
||||
css_freeze @t 10 # Freeze all terrorists for 10 seconds
|
||||
```
|
||||
|
||||
**Effects:**
|
||||
- Player cannot move
|
||||
- Player cannot shoot
|
||||
- Auto-unfreezes after duration (if specified)
|
||||
|
||||
---
|
||||
|
||||
### Unfreeze
|
||||
|
||||
Unfreeze a frozen player.
|
||||
|
||||
```bash
|
||||
css_unfreeze <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_unfreeze #123
|
||||
css_unfreeze PlayerName
|
||||
css_unfreeze @all # Unfreeze everyone
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Respawn
|
||||
|
||||
Respawn a dead player at last death position.
|
||||
|
||||
```bash
|
||||
css_respawn <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cheats`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_respawn #123
|
||||
css_respawn PlayerName
|
||||
css_respawn @dead # Respawn all dead players
|
||||
```
|
||||
|
||||
**Effects:**
|
||||
- Player spawns at death point
|
||||
- Gets default weapons
|
||||
- Joins their team
|
||||
|
||||
---
|
||||
|
||||
### Give Weapon
|
||||
|
||||
Give a weapon to a player.
|
||||
|
||||
```bash
|
||||
css_give <#userid or name> <weapon>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cheats`
|
||||
|
||||
**Weapon names:**
|
||||
|
||||
**Rifles:**
|
||||
- `weapon_ak47` or `ak47`
|
||||
- `weapon_m4a1` or `m4a1`
|
||||
- `weapon_m4a1_silencer` or `m4a1_silencer`
|
||||
- `weapon_awp` or `awp`
|
||||
- `weapon_aug` or `aug`
|
||||
- `weapon_sg556` or `sg556`
|
||||
- `weapon_ssg08` or `ssg08` (Scout)
|
||||
- `weapon_g3sg1` or `g3sg1`
|
||||
- `weapon_scar20` or `scar20`
|
||||
|
||||
**SMGs:**
|
||||
- `weapon_mp5sd` or `mp5sd`
|
||||
- `weapon_mp7` or `mp7`
|
||||
- `weapon_mp9` or `mp9`
|
||||
- `weapon_mac10` or `mac10`
|
||||
- `weapon_p90` or `p90`
|
||||
- `weapon_ump45` or `ump45`
|
||||
- `weapon_bizon` or `bizon`
|
||||
|
||||
**Heavy:**
|
||||
- `weapon_nova` or `nova`
|
||||
- `weapon_xm1014` or `xm1014`
|
||||
- `weapon_mag7` or `mag7`
|
||||
- `weapon_sawedoff` or `sawedoff`
|
||||
- `weapon_m249` or `m249`
|
||||
- `weapon_negev` or `negev`
|
||||
|
||||
**Pistols:**
|
||||
- `weapon_deagle` or `deagle`
|
||||
- `weapon_elite` or `elite` (Dual Berettas)
|
||||
- `weapon_fiveseven` or `fiveseven`
|
||||
- `weapon_glock` or `glock`
|
||||
- `weapon_hkp2000` or `hkp2000`
|
||||
- `weapon_p250` or `p250`
|
||||
- `weapon_usp_silencer` or `usp_silencer`
|
||||
- `weapon_tec9` or `tec9`
|
||||
- `weapon_cz75a` or `cz75a`
|
||||
- `weapon_revolver` or `revolver`
|
||||
|
||||
**Grenades:**
|
||||
- `weapon_flashbang` or `flashbang`
|
||||
- `weapon_hegrenade` or `hegrenade`
|
||||
- `weapon_smokegrenade` or `smokegrenade`
|
||||
- `weapon_molotov` or `molotov`
|
||||
- `weapon_incgrenade` or `incgrenade`
|
||||
- `weapon_decoy` or `decoy`
|
||||
|
||||
**Equipment:**
|
||||
- `weapon_knife` or `knife`
|
||||
- `weapon_taser` or `taser`
|
||||
- `item_defuser` or `defuser`
|
||||
- `item_kevlar` or `kevlar`
|
||||
- `item_assaultsuit` or `assaultsuit`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_give #123 awp
|
||||
css_give PlayerName ak47
|
||||
css_give @ct m4a1
|
||||
css_give @all deagle
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Strip Weapons
|
||||
|
||||
Remove all weapons from a player.
|
||||
|
||||
```bash
|
||||
css_strip <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_strip #123
|
||||
css_strip PlayerName
|
||||
css_strip @t # Disarm all terrorists
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Set HP
|
||||
|
||||
Set a player's health.
|
||||
|
||||
```bash
|
||||
css_hp <#userid or name> <health>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `health` - Health amount (1-999+)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_hp #123 100 # Full health
|
||||
css_hp PlayerName 200 # 200 HP
|
||||
css_hp @all 1 # 1 HP everyone
|
||||
```
|
||||
|
||||
**Common values:**
|
||||
- `1` - 1 HP (one-shot mode)
|
||||
- `100` - Normal health
|
||||
- `200` - Double health
|
||||
- `500` - Tank mode
|
||||
|
||||
---
|
||||
|
||||
### Set Speed
|
||||
|
||||
Modify a player's movement speed.
|
||||
|
||||
```bash
|
||||
css_speed <#userid or name> <speed>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `speed` - Speed multiplier (0.1 - 10.0)
|
||||
- `1.0` = Normal speed
|
||||
- `2.0` = Double speed
|
||||
- `0.5` = Half speed
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_speed #123 1.5 # 50% faster
|
||||
css_speed PlayerName 0.5 # Slow motion
|
||||
css_speed @all 2.0 # Everyone fast
|
||||
css_speed #123 1.0 # Reset to normal
|
||||
```
|
||||
|
||||
**Common values:**
|
||||
- `0.5` - Slow motion mode
|
||||
- `1.0` - Normal (reset)
|
||||
- `1.5` - Fast mode
|
||||
- `2.0` - Super fast
|
||||
- `3.0` - Extremely fast
|
||||
|
||||
---
|
||||
|
||||
### Set Gravity
|
||||
|
||||
Modify a player's gravity.
|
||||
|
||||
```bash
|
||||
css_gravity <#userid or name> <gravity>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `gravity` - Gravity multiplier (0.1 - 10.0)
|
||||
- `1.0` = Normal gravity
|
||||
- `0.5` = Moon jump
|
||||
- `2.0` = Heavy
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_gravity #123 0.5 # Moon jump
|
||||
css_gravity PlayerName 0.1 # Super jump
|
||||
css_gravity @all 2.0 # Heavy gravity
|
||||
css_gravity #123 1.0 # Reset to normal
|
||||
```
|
||||
|
||||
**Common values:**
|
||||
- `0.1` - Super high jumps
|
||||
- `0.5` - Moon gravity
|
||||
- `1.0` - Normal (reset)
|
||||
- `2.0` - Heavy/fast falling
|
||||
|
||||
---
|
||||
|
||||
### Set Money
|
||||
|
||||
Set a player's money amount.
|
||||
|
||||
```bash
|
||||
css_money <#userid or name> <amount>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `amount` - Money amount (0-65535)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_money #123 16000 # Max money
|
||||
css_money PlayerName 0 # Remove all money
|
||||
css_money @ct 10000 # Give all CTs $10,000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Resize Player
|
||||
|
||||
Change a player's model size.
|
||||
|
||||
```bash
|
||||
css_resize <#userid or name> <scale>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `scale` - Size scale (0.1 - 10.0)
|
||||
- `1.0` = Normal size
|
||||
- `0.5` = Half size
|
||||
- `2.0` = Double size
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_resize #123 0.5 # Tiny player
|
||||
css_resize PlayerName 2.0 # Giant player
|
||||
css_resize #123 1.0 # Reset to normal
|
||||
```
|
||||
|
||||
**Common values:**
|
||||
- `0.5` - Tiny mode
|
||||
- `1.0` - Normal (reset)
|
||||
- `1.5` - Big
|
||||
- `2.0` - Giant
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration file location:
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin_FunCommands/CS2-SimpleAdmin_FunCommands.json
|
||||
```
|
||||
|
||||
### Default Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": 1,
|
||||
"GodCommands": ["css_god", "css_godmode"],
|
||||
"NoclipCommands": ["css_noclip"],
|
||||
"FreezeCommands": ["css_freeze"],
|
||||
"UnfreezeCommands": ["css_unfreeze"],
|
||||
"RespawnCommands": ["css_respawn"],
|
||||
"GiveCommands": ["css_give"],
|
||||
"StripCommands": ["css_strip"],
|
||||
"HpCommands": ["css_hp"],
|
||||
"SpeedCommands": ["css_speed"],
|
||||
"GravityCommands": ["css_gravity"],
|
||||
"MoneyCommands": ["css_money"],
|
||||
"ResizeCommands": ["css_resize"]
|
||||
}
|
||||
```
|
||||
|
||||
### Customizing Commands
|
||||
|
||||
**Add aliases:**
|
||||
```json
|
||||
"GodCommands": ["css_god", "css_godmode", "css_immortal"]
|
||||
```
|
||||
|
||||
**Disable feature:**
|
||||
```json
|
||||
"GodCommands": []
|
||||
```
|
||||
|
||||
**Rename command:**
|
||||
```json
|
||||
"NoclipCommands": ["css_fly"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Admin Menu Integration
|
||||
|
||||
The module automatically adds a "Fun Commands" category to the admin menu with these options:
|
||||
|
||||
- God Mode
|
||||
- No Clip
|
||||
- Freeze
|
||||
- Respawn
|
||||
- Give Weapon
|
||||
- Strip Weapons
|
||||
- Set HP
|
||||
- Set Speed
|
||||
- Set Gravity
|
||||
- Set Money
|
||||
- Resize Player
|
||||
|
||||
**Access menu:**
|
||||
```bash
|
||||
css_admin # Navigate to "Fun Commands"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Permission System
|
||||
|
||||
### Permission Override
|
||||
|
||||
Admins can override command permissions using CounterStrikeSharp's admin system.
|
||||
|
||||
**Example:**
|
||||
If you want VIPs to use god mode:
|
||||
|
||||
1. **In admin config**, add permission override for `css_god`:
|
||||
```json
|
||||
{
|
||||
"css_god": ["@css/vip"]
|
||||
}
|
||||
```
|
||||
|
||||
2. **VIPs will now see God Mode** in the menu
|
||||
|
||||
---
|
||||
|
||||
## Permissions Required
|
||||
|
||||
| Command | Default Permission | Description |
|
||||
|---------|------------------|-------------|
|
||||
| `css_god` | `@css/cheats` | God mode |
|
||||
| `css_noclip` | `@css/cheats` | No clip |
|
||||
| `css_freeze` | `@css/slay` | Freeze players |
|
||||
| `css_unfreeze` | `@css/slay` | Unfreeze players |
|
||||
| `css_respawn` | `@css/cheats` | Respawn players |
|
||||
| `css_give` | `@css/cheats` | Give weapons |
|
||||
| `css_strip` | `@css/slay` | Strip weapons |
|
||||
| `css_hp` | `@css/slay` | Set health |
|
||||
| `css_speed` | `@css/slay` | Set speed |
|
||||
| `css_gravity` | `@css/slay` | Set gravity |
|
||||
| `css_money` | `@css/slay` | Set money |
|
||||
| `css_resize` | `@css/slay` | Resize player |
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Fun Rounds
|
||||
|
||||
```bash
|
||||
# Low gravity, high speed round
|
||||
css_gravity @all 0.3
|
||||
css_speed @all 1.5
|
||||
|
||||
# One-shot mode
|
||||
css_hp @all 1
|
||||
css_give @all deagle
|
||||
|
||||
# Tiny players
|
||||
css_resize @all 0.5
|
||||
```
|
||||
|
||||
### Admin Events
|
||||
|
||||
```bash
|
||||
# Hide and seek (seekers)
|
||||
css_speed @ct 1.5
|
||||
css_hp @ct 200
|
||||
|
||||
# Hide and seek (hiders)
|
||||
css_resize @t 0.5
|
||||
css_speed @t 0.8
|
||||
```
|
||||
|
||||
### Testing & Debug
|
||||
|
||||
```bash
|
||||
# Test map navigation
|
||||
css_noclip @me
|
||||
css_god @me
|
||||
|
||||
# Test weapon balance
|
||||
css_give @me awp
|
||||
css_hp @me 100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Competitive Balance
|
||||
|
||||
1. **Don't use during serious matches** - Breaks game balance
|
||||
2. **Announce fun rounds** - Let players know it's for fun
|
||||
3. **Reset after use** - Return to normal settings
|
||||
4. **Save for appropriate times** - End of night, special events
|
||||
|
||||
### Reset Commands
|
||||
|
||||
Always reset modifications after fun rounds:
|
||||
|
||||
```bash
|
||||
css_speed @all 1.0
|
||||
css_gravity @all 1.0
|
||||
css_resize @all 1.0
|
||||
```
|
||||
|
||||
### Permission Management
|
||||
|
||||
1. **Limit @css/cheats** - Only trusted admins
|
||||
2. **@css/slay is safer** - For HP/speed/gravity
|
||||
3. **Monitor usage** - Check logs for abuse
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Speed/Gravity not persisting
|
||||
|
||||
**Solution:**
|
||||
- These are maintained by a repeating timer
|
||||
- If they reset, reapply them
|
||||
- Check server console for timer errors
|
||||
|
||||
### God mode not working
|
||||
|
||||
**Check:**
|
||||
- Is player alive?
|
||||
- Check console for errors
|
||||
- Try toggling off and on
|
||||
|
||||
### Can't give weapons
|
||||
|
||||
**Check:**
|
||||
- Correct weapon name
|
||||
- Player is alive
|
||||
- Player has inventory space
|
||||
|
||||
### Noclip doesn't work
|
||||
|
||||
**Check:**
|
||||
- Player must be alive
|
||||
- sv_cheats doesn't need to be enabled
|
||||
- Check console for errors
|
||||
|
||||
---
|
||||
|
||||
## Module Development
|
||||
|
||||
This module serves as a **reference implementation** for creating CS2-SimpleAdmin modules.
|
||||
|
||||
**Key concepts demonstrated:**
|
||||
- Command registration from configuration
|
||||
- Menu creation with SimpleAdmin API
|
||||
- Per-player translation support
|
||||
- Proper cleanup on module unload
|
||||
- Code organization using partial classes
|
||||
|
||||
**[View source code](https://github.com/daffyyyy/CS2-SimpleAdmin/tree/main/Modules/CS2-SimpleAdmin_FunCommands)** for implementation details.
|
||||
|
||||
---
|
||||
|
||||
## Translations
|
||||
|
||||
The module includes translations for 13 languages:
|
||||
|
||||
- English (en)
|
||||
- Polish (pl)
|
||||
- Russian (ru)
|
||||
- Portuguese (pt)
|
||||
- And 9 more...
|
||||
|
||||
Translation files location:
|
||||
```
|
||||
plugins/CS2-SimpleAdmin_FunCommands/lang/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **[Player Commands](../user/commands/playercommands)** - Core player commands
|
||||
- **[Module Development](development)** - Create your own modules
|
||||
- **[API Reference](../developer/api/overview)** - CS2-SimpleAdmin API
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
**v1.0.0** - Initial release
|
||||
- God mode
|
||||
- Noclip
|
||||
- Freeze/Unfreeze
|
||||
- Respawn
|
||||
- Give/Strip weapons
|
||||
- HP/Speed/Gravity/Money
|
||||
- Resize player
|
||||
- Admin menu integration
|
||||
- 13 language support
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
**Issues:** [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
|
||||
|
||||
**Questions:** [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
||||
260
CS2-SimpleAdmin-docs/docs/modules/intro.md
Normal file
@@ -0,0 +1,260 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Modules Introduction
|
||||
|
||||
Extend CS2-SimpleAdmin functionality with powerful modules.
|
||||
|
||||
## What are Modules?
|
||||
|
||||
Modules are extensions that add new features to CS2-SimpleAdmin. They use the CS2-SimpleAdmin API to integrate seamlessly with the core plugin.
|
||||
|
||||
## Official Modules
|
||||
|
||||
### Fun Commands Module
|
||||
|
||||
Adds entertainment and player manipulation commands like god mode, noclip, freeze, and more.
|
||||
|
||||
**[Learn more →](funcommands)**
|
||||
|
||||
---
|
||||
|
||||
## Benefits of Modules
|
||||
|
||||
### 🔌 Easy Integration
|
||||
- Built on CS2-SimpleAdmin API
|
||||
- Automatic menu registration
|
||||
- Command system integration
|
||||
|
||||
### 🎨 Feature Separation
|
||||
- Keep core plugin lightweight
|
||||
- Add only features you need
|
||||
- Easy to enable/disable
|
||||
|
||||
### 🔧 Customizable
|
||||
- Configure each module independently
|
||||
- Disable unwanted commands
|
||||
- Customize permissions
|
||||
|
||||
### 📦 Simple Installation
|
||||
- Drop module files in folder
|
||||
- Restart server
|
||||
- Module auto-loads
|
||||
|
||||
---
|
||||
|
||||
## Installing Modules
|
||||
|
||||
### Standard Installation
|
||||
|
||||
1. **Download the module** from releases or build from source
|
||||
|
||||
2. **Extract to plugins folder:**
|
||||
```
|
||||
game/csgo/addons/counterstrikesharp/plugins/ModuleName/
|
||||
```
|
||||
|
||||
3. **Restart server** or reload plugins:
|
||||
```
|
||||
css_plugins reload
|
||||
```
|
||||
|
||||
4. **Configure** (if needed):
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/ModuleName/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Structure
|
||||
|
||||
Typical module structure:
|
||||
|
||||
```
|
||||
plugins/
|
||||
└── CS2-SimpleAdmin_ModuleName/
|
||||
├── CS2-SimpleAdmin_ModuleName.dll
|
||||
├── CS2-SimpleAdmin_ModuleName.json (config)
|
||||
└── lang/ (translations)
|
||||
├── en.json
|
||||
├── pl.json
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Configuration
|
||||
|
||||
Each module has its own configuration file:
|
||||
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/ModuleName/ModuleName.json
|
||||
```
|
||||
|
||||
### Common Configuration Pattern
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": 1,
|
||||
"CommandName": ["css_command", "css_alias"],
|
||||
"OtherSettings": {
|
||||
"EnableFeature": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Command lists allow multiple aliases
|
||||
- Empty command list = feature disabled
|
||||
- Module-specific settings
|
||||
|
||||
---
|
||||
|
||||
## Available Modules
|
||||
|
||||
### Core Modules
|
||||
|
||||
| Module | Description | Status |
|
||||
|--------|-------------|--------|
|
||||
| **[Fun Commands](funcommands)** | God mode, noclip, freeze, speed, gravity | ✅ Official |
|
||||
|
||||
### Community Modules
|
||||
|
||||
Check the [GitHub repository](https://github.com/daffyyyy/CS2-SimpleAdmin) for community-contributed modules.
|
||||
|
||||
---
|
||||
|
||||
## Developing Modules
|
||||
|
||||
Want to create your own module?
|
||||
|
||||
**[See Module Development Guide →](development)**
|
||||
|
||||
**[See Developer Documentation →](../developer/intro)**
|
||||
|
||||
---
|
||||
|
||||
## Module vs Core Plugin
|
||||
|
||||
### When to use Core Plugin:
|
||||
- Essential admin functions
|
||||
- Punishment system
|
||||
- Permission management
|
||||
- Database operations
|
||||
|
||||
### When to use Modules:
|
||||
- Optional features
|
||||
- Server-specific functionality
|
||||
- Experimental features
|
||||
- Custom integrations
|
||||
|
||||
---
|
||||
|
||||
## Module Dependencies
|
||||
|
||||
### Required for All Modules:
|
||||
- CS2-SimpleAdmin (core plugin)
|
||||
- CS2-SimpleAdminApi.dll
|
||||
|
||||
### Module-Specific:
|
||||
Check each module's documentation for specific requirements.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting Modules
|
||||
|
||||
### Module doesn't load
|
||||
|
||||
**Check:**
|
||||
1. Is CS2-SimpleAdmin loaded?
|
||||
2. Is CS2-SimpleAdminApi.dll in shared folder?
|
||||
3. Check server console for errors
|
||||
4. Verify module files are complete
|
||||
|
||||
### Module commands not working
|
||||
|
||||
**Check:**
|
||||
1. Is command enabled in module config?
|
||||
2. Do you have required permissions?
|
||||
3. Check Commands.json for conflicts
|
||||
4. Verify module loaded successfully
|
||||
|
||||
### Module conflicts
|
||||
|
||||
**Check:**
|
||||
- Multiple modules providing same command
|
||||
- Check server console for warnings
|
||||
- Disable conflicting module
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Module Management
|
||||
|
||||
1. **Use only needed modules** - Don't overload server
|
||||
2. **Keep modules updated** - Check for updates regularly
|
||||
3. **Test before production** - Test modules on dev server first
|
||||
4. **Review permissions** - Understand what each module can do
|
||||
|
||||
### Performance
|
||||
|
||||
1. **Monitor resource usage** - Some modules may impact performance
|
||||
2. **Configure wisely** - Disable unused features
|
||||
3. **Check logs** - Monitor for errors
|
||||
|
||||
---
|
||||
|
||||
## Module Updates
|
||||
|
||||
### Updating Modules
|
||||
|
||||
1. **Backup current version**
|
||||
2. **Download new version**
|
||||
3. **Replace files** in plugins folder
|
||||
4. **Check configuration** - New config options may exist
|
||||
5. **Restart server**
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
Some updates may have breaking changes:
|
||||
- Check module changelog
|
||||
- Review new configuration options
|
||||
- Test thoroughly
|
||||
|
||||
---
|
||||
|
||||
## Community Contributions
|
||||
|
||||
### Sharing Modules
|
||||
|
||||
Created a module? Share it with the community!
|
||||
|
||||
1. **Publish on GitHub**
|
||||
2. **Document thoroughly**
|
||||
3. **Provide examples**
|
||||
4. **Include README**
|
||||
|
||||
### Using Community Modules
|
||||
|
||||
1. **Review code** - Ensure it's safe
|
||||
2. **Check compatibility** - Verify CS2-SimpleAdmin version
|
||||
3. **Test thoroughly** - Don't trust blindly
|
||||
4. **Report issues** - Help improve modules
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Explore Fun Commands Module](funcommands)** - Add entertainment features
|
||||
- **[Learn Module Development](development)** - Create your own modules
|
||||
- **[Read API Documentation](../developer/intro)** - Understand the API
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
- **Issues** - [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
|
||||
- **Discussions** - [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
||||
- **Examples** - Check official modules for reference
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Tutorial - Basics",
|
||||
"position": 2,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "5 minutes to learn the most important Docusaurus concepts."
|
||||
}
|
||||
}
|
||||
23
CS2-SimpleAdmin-docs/docs/tutorial-basics/congratulations.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Congratulations!
|
||||
|
||||
You have just learned the **basics of Docusaurus** and made some changes to the **initial template**.
|
||||
|
||||
Docusaurus has **much more to offer**!
|
||||
|
||||
Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**.
|
||||
|
||||
Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610)
|
||||
|
||||
## What's next?
|
||||
|
||||
- Read the [official documentation](https://docusaurus.io/)
|
||||
- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config)
|
||||
- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration)
|
||||
- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
|
||||
- Add a [search bar](https://docusaurus.io/docs/search)
|
||||
- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase)
|
||||
- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support)
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Create a Blog Post
|
||||
|
||||
Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
|
||||
|
||||
## Create your first Post
|
||||
|
||||
Create a file at `blog/2021-02-28-greetings.md`:
|
||||
|
||||
```md title="blog/2021-02-28-greetings.md"
|
||||
---
|
||||
slug: greetings
|
||||
title: Greetings!
|
||||
authors:
|
||||
- name: Joel Marcey
|
||||
title: Co-creator of Docusaurus 1
|
||||
url: https://github.com/JoelMarcey
|
||||
image_url: https://github.com/JoelMarcey.png
|
||||
- name: Sébastien Lorber
|
||||
title: Docusaurus maintainer
|
||||
url: https://sebastienlorber.com
|
||||
image_url: https://github.com/slorber.png
|
||||
tags: [greetings]
|
||||
---
|
||||
|
||||
Congratulations, you have made your first post!
|
||||
|
||||
Feel free to play around and edit this post as much as you like.
|
||||
```
|
||||
|
||||
A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Create a Document
|
||||
|
||||
Documents are **groups of pages** connected through:
|
||||
|
||||
- a **sidebar**
|
||||
- **previous/next navigation**
|
||||
- **versioning**
|
||||
|
||||
## Create your first Doc
|
||||
|
||||
Create a Markdown file at `docs/hello.md`:
|
||||
|
||||
```md title="docs/hello.md"
|
||||
# Hello
|
||||
|
||||
This is my **first Docusaurus document**!
|
||||
```
|
||||
|
||||
A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello).
|
||||
|
||||
## Configure the Sidebar
|
||||
|
||||
Docusaurus automatically **creates a sidebar** from the `docs` folder.
|
||||
|
||||
Add metadata to customize the sidebar label and position:
|
||||
|
||||
```md title="docs/hello.md" {1-4}
|
||||
---
|
||||
sidebar_label: 'Hi!'
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Hello
|
||||
|
||||
This is my **first Docusaurus document**!
|
||||
```
|
||||
|
||||
It is also possible to create your sidebar explicitly in `sidebars.js`:
|
||||
|
||||
```js title="sidebars.js"
|
||||
export default {
|
||||
tutorialSidebar: [
|
||||
'intro',
|
||||
// highlight-next-line
|
||||
'hello',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tutorial',
|
||||
items: ['tutorial-basics/create-a-document'],
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
43
CS2-SimpleAdmin-docs/docs/tutorial-basics/create-a-page.md
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Create a Page
|
||||
|
||||
Add **Markdown or React** files to `src/pages` to create a **standalone page**:
|
||||
|
||||
- `src/pages/index.js` → `localhost:3000/`
|
||||
- `src/pages/foo.md` → `localhost:3000/foo`
|
||||
- `src/pages/foo/bar.js` → `localhost:3000/foo/bar`
|
||||
|
||||
## Create your first React Page
|
||||
|
||||
Create a file at `src/pages/my-react-page.js`:
|
||||
|
||||
```jsx title="src/pages/my-react-page.js"
|
||||
import React from 'react';
|
||||
import Layout from '@theme/Layout';
|
||||
|
||||
export default function MyReactPage() {
|
||||
return (
|
||||
<Layout>
|
||||
<h1>My React page</h1>
|
||||
<p>This is a React page</p>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page).
|
||||
|
||||
## Create your first Markdown Page
|
||||
|
||||
Create a file at `src/pages/my-markdown-page.md`:
|
||||
|
||||
```mdx title="src/pages/my-markdown-page.md"
|
||||
# My Markdown page
|
||||
|
||||
This is a Markdown page
|
||||
```
|
||||
|
||||
A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page).
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Deploy your site
|
||||
|
||||
Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**).
|
||||
|
||||
It builds your site as simple **static HTML, JavaScript and CSS files**.
|
||||
|
||||
## Build your site
|
||||
|
||||
Build your site **for production**:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
The static files are generated in the `build` folder.
|
||||
|
||||
## Deploy your site
|
||||
|
||||
Test your production build locally:
|
||||
|
||||
```bash
|
||||
npm run serve
|
||||
```
|
||||
|
||||
The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/).
|
||||
|
||||
You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**).
|
||||
152
CS2-SimpleAdmin-docs/docs/tutorial-basics/markdown-features.mdx
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Markdown Features
|
||||
|
||||
Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
|
||||
|
||||
## Front Matter
|
||||
|
||||
Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
|
||||
|
||||
```text title="my-doc.md"
|
||||
// highlight-start
|
||||
---
|
||||
id: my-doc-id
|
||||
title: My document title
|
||||
description: My document description
|
||||
slug: /my-custom-url
|
||||
---
|
||||
// highlight-end
|
||||
|
||||
## Markdown heading
|
||||
|
||||
Markdown text with [links](./hello.md)
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
Regular Markdown links are supported, using url paths or relative file paths.
|
||||
|
||||
```md
|
||||
Let's see how to [Create a page](/create-a-page).
|
||||
```
|
||||
|
||||
```md
|
||||
Let's see how to [Create a page](./create-a-page.md).
|
||||
```
|
||||
|
||||
**Result:** Let's see how to [Create a page](./create-a-page.md).
|
||||
|
||||
## Images
|
||||
|
||||
Regular Markdown images are supported.
|
||||
|
||||
You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`):
|
||||
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||

|
||||
|
||||
You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them:
|
||||
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||
## Code Blocks
|
||||
|
||||
Markdown code blocks are supported with Syntax highlighting.
|
||||
|
||||
````md
|
||||
```jsx title="src/components/HelloDocusaurus.js"
|
||||
function HelloDocusaurus() {
|
||||
return <h1>Hello, Docusaurus!</h1>;
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
```jsx title="src/components/HelloDocusaurus.js"
|
||||
function HelloDocusaurus() {
|
||||
return <h1>Hello, Docusaurus!</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
## Admonitions
|
||||
|
||||
Docusaurus has a special syntax to create admonitions and callouts:
|
||||
|
||||
```md
|
||||
:::tip My tip
|
||||
|
||||
Use this awesome feature option
|
||||
|
||||
:::
|
||||
|
||||
:::danger Take care
|
||||
|
||||
This action is dangerous
|
||||
|
||||
:::
|
||||
```
|
||||
|
||||
:::tip My tip
|
||||
|
||||
Use this awesome feature option
|
||||
|
||||
:::
|
||||
|
||||
:::danger Take care
|
||||
|
||||
This action is dangerous
|
||||
|
||||
:::
|
||||
|
||||
## MDX and React Components
|
||||
|
||||
[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**:
|
||||
|
||||
```jsx
|
||||
export const Highlight = ({children, color}) => (
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
borderRadius: '20px',
|
||||
color: '#fff',
|
||||
padding: '10px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
alert(`You clicked the color ${color} with label ${children}`)
|
||||
}}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
|
||||
|
||||
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
|
||||
```
|
||||
|
||||
export const Highlight = ({children, color}) => (
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
borderRadius: '20px',
|
||||
color: '#fff',
|
||||
padding: '10px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
alert(`You clicked the color ${color} with label ${children}`);
|
||||
}}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
|
||||
|
||||
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"label": "Tutorial - Extras",
|
||||
"position": 3,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 25 KiB |
BIN
CS2-SimpleAdmin-docs/docs/tutorial-extras/img/localeDropdown.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1,55 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Manage Docs Versions
|
||||
|
||||
Docusaurus can manage multiple versions of your docs.
|
||||
|
||||
## Create a docs version
|
||||
|
||||
Release a version 1.0 of your project:
|
||||
|
||||
```bash
|
||||
npm run docusaurus docs:version 1.0
|
||||
```
|
||||
|
||||
The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created.
|
||||
|
||||
Your docs now have 2 versions:
|
||||
|
||||
- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs
|
||||
- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs**
|
||||
|
||||
## Add a Version Dropdown
|
||||
|
||||
To navigate seamlessly across versions, add a version dropdown.
|
||||
|
||||
Modify the `docusaurus.config.js` file:
|
||||
|
||||
```js title="docusaurus.config.js"
|
||||
export default {
|
||||
themeConfig: {
|
||||
navbar: {
|
||||
items: [
|
||||
// highlight-start
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
},
|
||||
// highlight-end
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
The docs version dropdown appears in your navbar:
|
||||
|
||||

|
||||
|
||||
## Update an existing version
|
||||
|
||||
It is possible to edit versioned docs in their respective folder:
|
||||
|
||||
- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello`
|
||||
- `docs/hello.md` updates `http://localhost:3000/docs/next/hello`
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Translate your site
|
||||
|
||||
Let's translate `docs/intro.md` to French.
|
||||
|
||||
## Configure i18n
|
||||
|
||||
Modify `docusaurus.config.js` to add support for the `fr` locale:
|
||||
|
||||
```js title="docusaurus.config.js"
|
||||
export default {
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'fr'],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Translate a doc
|
||||
|
||||
Copy the `docs/intro.md` file to the `i18n/fr` folder:
|
||||
|
||||
```bash
|
||||
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
|
||||
|
||||
cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
|
||||
```
|
||||
|
||||
Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French.
|
||||
|
||||
## Start your localized site
|
||||
|
||||
Start your site on the French locale:
|
||||
|
||||
```bash
|
||||
npm run start -- --locale fr
|
||||
```
|
||||
|
||||
Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated.
|
||||
|
||||
:::caution
|
||||
|
||||
In development, you can only use one locale at a time.
|
||||
|
||||
:::
|
||||
|
||||
## Add a Locale Dropdown
|
||||
|
||||
To navigate seamlessly across languages, add a locale dropdown.
|
||||
|
||||
Modify the `docusaurus.config.js` file:
|
||||
|
||||
```js title="docusaurus.config.js"
|
||||
export default {
|
||||
themeConfig: {
|
||||
navbar: {
|
||||
items: [
|
||||
// highlight-start
|
||||
{
|
||||
type: 'localeDropdown',
|
||||
},
|
||||
// highlight-end
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
The locale dropdown now appears in your navbar:
|
||||
|
||||

|
||||
|
||||
## Build your localized site
|
||||
|
||||
Build your site for a specific locale:
|
||||
|
||||
```bash
|
||||
npm run build -- --locale fr
|
||||
```
|
||||
|
||||
Or build your site to include all the locales at once:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
262
CS2-SimpleAdmin-docs/docs/user/commands/basebans.md
Normal file
@@ -0,0 +1,262 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Ban Commands
|
||||
|
||||
Commands for managing player bans.
|
||||
|
||||
## Ban Player
|
||||
|
||||
Ban a player currently on the server.
|
||||
|
||||
```bash
|
||||
css_ban <#userid or name> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/ban`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_ban @all 60 "Timeout for everyone"
|
||||
css_ban #123 1440 "Hacking - 1 day ban"
|
||||
css_ban PlayerName 0 "Permanent ban for cheating"
|
||||
css_ban @ct 30 "CT team timeout"
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Time in minutes (0 = permanent)
|
||||
- Supports player targeting (@all, @ct, @t, #userid, name)
|
||||
- Reason is optional but recommended
|
||||
|
||||
---
|
||||
|
||||
## Add Ban (Offline Player)
|
||||
|
||||
Ban a player by SteamID even if they're not online.
|
||||
|
||||
```bash
|
||||
css_addban <steamid> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/ban`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_addban STEAM_1:0:12345678 1440 "Ban evasion"
|
||||
css_addban 76561198012345678 10080 "Hacking - 7 day ban"
|
||||
css_addban STEAM_1:1:87654321 0 "Permanent ban"
|
||||
```
|
||||
|
||||
**Supported SteamID formats:**
|
||||
- SteamID64: `76561198012345678`
|
||||
- SteamID: `STEAM_1:0:12345678`
|
||||
- SteamID3: `[U:1:12345678]`
|
||||
|
||||
---
|
||||
|
||||
## Ban IP Address
|
||||
|
||||
Ban an IP address.
|
||||
|
||||
```bash
|
||||
css_banip <ip> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/ban`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_banip 192.168.1.100 1440 "Ban evasion attempt"
|
||||
css_banip 10.0.0.5 0 "Persistent troublemaker"
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Useful for preventing ban evasion
|
||||
- Can be combined with SteamID bans
|
||||
- Check config for `BanType` setting (SteamID, IP, or Both)
|
||||
|
||||
---
|
||||
|
||||
## Unban Player
|
||||
|
||||
Remove a ban from a player.
|
||||
|
||||
```bash
|
||||
css_unban <steamid or name or ip> [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/unban`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_unban 76561198012345678 "Appeal accepted"
|
||||
css_unban STEAM_1:0:12345678 "Ban lifted"
|
||||
css_unban 192.168.1.100 "Wrong person banned"
|
||||
css_unban PlayerName "Mistake"
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Works with SteamID, IP, or player name
|
||||
- Unban reason is logged
|
||||
- Can unban offline players
|
||||
|
||||
---
|
||||
|
||||
## Warn Player
|
||||
|
||||
Issue a warning to a player.
|
||||
|
||||
```bash
|
||||
css_warn <#userid or name> [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_warn #123 "Mic spam"
|
||||
css_warn PlayerName "Language"
|
||||
css_warn @all "Final warning"
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Warnings can accumulate
|
||||
- Auto-escalation to bans based on `WarnThreshold` config
|
||||
- Example: 3 warnings = 1 hour ban, 4 warnings = 2 hour ban
|
||||
|
||||
**Warning Threshold Configuration:**
|
||||
```json
|
||||
"WarnThreshold": {
|
||||
"3": "css_addban STEAMID64 60 \"3 warnings\"",
|
||||
"4": "css_ban #USERID 120 \"4 warnings\""
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Unwarn Player
|
||||
|
||||
Remove a warning from a player.
|
||||
|
||||
```bash
|
||||
css_unwarn <steamid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_unwarn 76561198012345678
|
||||
css_unwarn PlayerName
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Removes the most recent warning
|
||||
- Helps manage warning thresholds
|
||||
- Can be used for offline players
|
||||
|
||||
---
|
||||
|
||||
## Permission Requirements
|
||||
|
||||
| Command | Required Permission | Description |
|
||||
|---------|-------------------|-------------|
|
||||
| `css_ban` | `@css/ban` | Ban online players |
|
||||
| `css_addban` | `@css/ban` | Ban offline players by SteamID |
|
||||
| `css_banip` | `@css/ban` | Ban IP addresses |
|
||||
| `css_unban` | `@css/unban` | Remove bans |
|
||||
| `css_warn` | `@css/kick` | Issue warnings |
|
||||
| `css_unwarn` | `@css/kick` | Remove warnings |
|
||||
|
||||
## Ban Types
|
||||
|
||||
Configure ban behavior in `CS2-SimpleAdmin.json`:
|
||||
|
||||
```json
|
||||
"BanType": 1
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `1` - SteamID only (default)
|
||||
- `2` - IP only
|
||||
- `3` - Both SteamID and IP
|
||||
|
||||
## Time Durations
|
||||
|
||||
Common time values:
|
||||
|
||||
| Duration | Minutes | Description |
|
||||
|----------|---------|-------------|
|
||||
| 1 minute | 1 | Very short timeout |
|
||||
| 5 minutes | 5 | Short timeout |
|
||||
| 15 minutes | 15 | Medium timeout |
|
||||
| 1 hour | 60 | Standard timeout |
|
||||
| 1 day | 1440 | Daily ban |
|
||||
| 1 week | 10080 | Weekly ban |
|
||||
| 2 weeks | 20160 | Bi-weekly ban |
|
||||
| 1 month | 43200 | Monthly ban |
|
||||
| Permanent | 0 | Never expires |
|
||||
|
||||
## Player Targeting
|
||||
|
||||
All ban commands support advanced targeting:
|
||||
|
||||
- `@all` - Target all players
|
||||
- `@ct` - Target all Counter-Terrorists
|
||||
- `@t` - Target all Terrorists
|
||||
- `@spec` - Target all spectators
|
||||
- `#123` - Target by userid
|
||||
- `PlayerName` - Target by name (partial match)
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Banning
|
||||
|
||||
1. **Always provide a reason** - Helps with appeals and record keeping
|
||||
2. **Use appropriate durations** - Don't permaban for minor offenses
|
||||
3. **Check ban history** - Use `css_who` to see if player has priors
|
||||
4. **Consider warnings first** - Give players a chance to improve
|
||||
|
||||
### Warning System
|
||||
|
||||
1. **Be consistent** - Use warnings for minor offenses
|
||||
2. **Configure thresholds** - Set up auto-escalation in config
|
||||
3. **Communicate clearly** - Let players know why they're warned
|
||||
4. **Review regularly** - Check warning history with `css_warns`
|
||||
|
||||
### Multi-Account Detection
|
||||
|
||||
When `CheckMultiAccountsByIp` is enabled:
|
||||
- Plugin detects multiple accounts from same IP
|
||||
- Sends Discord notifications if configured
|
||||
- Helps identify ban evasion
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Ban doesn't work
|
||||
|
||||
**Check:**
|
||||
- Do you have `@css/ban` permission?
|
||||
- Is the SteamID format correct?
|
||||
- Check server console for errors
|
||||
|
||||
### Player rejoins after ban
|
||||
|
||||
**Check:**
|
||||
- Is `MultiServerMode` enabled if using multiple servers?
|
||||
- Is the database shared across servers?
|
||||
- Check ban type configuration (SteamID vs IP)
|
||||
|
||||
### Warning threshold not working
|
||||
|
||||
**Check:**
|
||||
- Is `WarnThreshold` configured correctly?
|
||||
- Are the command formats correct in config?
|
||||
- Check server console for execution errors
|
||||
|
||||
## Related Commands
|
||||
|
||||
- **[Communication Commands](basecomms)** - Mute, gag, silence
|
||||
- **[Player Commands](playercommands)** - Kick, slay, etc.
|
||||
- **[Base Commands](basecommands)** - Admin management
|
||||
442
CS2-SimpleAdmin-docs/docs/user/commands/basechat.md
Normal file
@@ -0,0 +1,442 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Chat Commands
|
||||
|
||||
Admin chat and messaging commands.
|
||||
|
||||
## Admin Chat
|
||||
|
||||
### Admin Say (Private)
|
||||
|
||||
Send a message to all online admins only.
|
||||
|
||||
```bash
|
||||
css_asay <message>
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Features:**
|
||||
- Only admins see the message
|
||||
- Useful for admin coordination
|
||||
- Colored differently from regular chat
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_asay "Player is suspicious, keep an eye on them"
|
||||
css_asay "I'm going AFK for 5 minutes"
|
||||
css_asay "Need help with a situation"
|
||||
```
|
||||
|
||||
**Message format:**
|
||||
```
|
||||
[ADMIN] YourName: message
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Public Announcements
|
||||
|
||||
### CSS Say (Colored)
|
||||
|
||||
Send a colored message to all players.
|
||||
|
||||
```bash
|
||||
css_cssay <message>
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Features:**
|
||||
- Colorful formatted message
|
||||
- Visible to all players
|
||||
- Stands out from regular chat
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_cssay "Server will restart in 5 minutes!"
|
||||
css_cssay "Welcome to our server!"
|
||||
css_cssay "Rules: No cheating, be respectful"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Say (With Prefix)
|
||||
|
||||
Send a message to all players with admin prefix.
|
||||
|
||||
```bash
|
||||
css_say <message>
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Features:**
|
||||
- Message shows with "(ADMIN)" prefix
|
||||
- Visible to all players
|
||||
- Authority message format
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_say "Please be respectful in chat"
|
||||
css_say "Cheating will result in permanent ban"
|
||||
css_say "Type !rules for server rules"
|
||||
```
|
||||
|
||||
**Message format:**
|
||||
```
|
||||
(ADMIN) YourName: message
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Private Say (Whisper)
|
||||
|
||||
Send a private message to a specific player.
|
||||
|
||||
```bash
|
||||
css_psay <#userid or name> <message>
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Features:**
|
||||
- Only the target player sees the message
|
||||
- Useful for private warnings or help
|
||||
- Doesn't clutter public chat
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_psay #123 "Please stop mic spamming"
|
||||
css_psay PlayerName "You need to join a team"
|
||||
css_psay @all "This is a private message to everyone"
|
||||
```
|
||||
|
||||
**Target receives:**
|
||||
```
|
||||
(ADMIN to you) AdminName: message
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Center Say
|
||||
|
||||
Display a message in the center of all players' screens.
|
||||
|
||||
```bash
|
||||
css_csay <message>
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Features:**
|
||||
- Large text in center of screen
|
||||
- Impossible to miss
|
||||
- Useful for important announcements
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_csay "ROUND STARTS IN 10 SECONDS"
|
||||
css_csay "FREEZE! Don't move!"
|
||||
css_csay "Server restarting NOW"
|
||||
```
|
||||
|
||||
**Display:**
|
||||
- Shows in center of screen
|
||||
- Large, bold text
|
||||
- Auto-fades after a few seconds
|
||||
|
||||
---
|
||||
|
||||
### HUD Say
|
||||
|
||||
Display a message on players' HUD (screen overlay).
|
||||
|
||||
```bash
|
||||
css_hsay <message>
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Features:**
|
||||
- Message appears on screen overlay
|
||||
- Less intrusive than center say
|
||||
- Stays visible longer
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_hsay "Tournament starting soon!"
|
||||
css_hsay "New map voting available"
|
||||
css_hsay "Visit our website: example.com"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Announcements
|
||||
|
||||
```bash
|
||||
# Server restart warning
|
||||
css_cssay "Server will restart in 10 minutes! Save your progress!"
|
||||
|
||||
# Center screen countdown
|
||||
css_csay "5"
|
||||
# Wait...
|
||||
css_csay "4"
|
||||
css_csay "3"
|
||||
css_csay "2"
|
||||
css_csay "1"
|
||||
css_csay "GO!"
|
||||
```
|
||||
|
||||
### Player Communication
|
||||
|
||||
```bash
|
||||
# Private warning
|
||||
css_psay PlayerName "This is your first warning for chat spam"
|
||||
|
||||
# Public announcement
|
||||
css_say "Everyone please be quiet for the next round"
|
||||
|
||||
# Admin coordination
|
||||
css_asay "I'm spectating the suspicious player in T spawn"
|
||||
```
|
||||
|
||||
### Event Management
|
||||
|
||||
```bash
|
||||
# Tournament announcement
|
||||
css_cssay "⚠ TOURNAMENT STARTING IN 5 MINUTES ⚠"
|
||||
css_hsay "Teams, please get ready!"
|
||||
css_csay "TOURNAMENT BEGINS NOW!"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Color Codes
|
||||
|
||||
Many chat commands support color codes:
|
||||
|
||||
```
|
||||
{default} - Default chat color
|
||||
{white} - White
|
||||
{darkred} - Dark red
|
||||
{green} - Green
|
||||
{lightyellow} - Light yellow
|
||||
{lightblue} - Light blue
|
||||
{olive} - Olive
|
||||
{lime} - Lime
|
||||
{red} - Red
|
||||
{purple} - Purple
|
||||
{grey} - Grey
|
||||
{yellow} - Yellow
|
||||
{gold} - Gold
|
||||
{silver} - Silver
|
||||
{blue} - Blue
|
||||
{darkblue} - Dark blue
|
||||
{bluegrey} - Blue grey
|
||||
{magenta} - Magenta
|
||||
{lightred} - Light red
|
||||
{orange} - Orange
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
css_say "{red}WARNING: {default}No camping in spawn!"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Message Targeting
|
||||
|
||||
Some commands support player targeting for private messages:
|
||||
|
||||
### Supported Targets
|
||||
|
||||
- `@all` - All players (private message to each)
|
||||
- `@ct` - All Counter-Terrorists
|
||||
- `@t` - All Terrorists
|
||||
- `@spec` - All spectators
|
||||
- `#123` - Specific userid
|
||||
- `PlayerName` - Player by name
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Private message to all CTs
|
||||
css_psay @ct "Defend bombsite A this round"
|
||||
|
||||
# Private message to all terrorists
|
||||
css_psay @t "Rush B with smoke and flash"
|
||||
|
||||
# Message to spectators
|
||||
css_psay @spec "Type !join to play"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When to Use Each Command
|
||||
|
||||
**css_asay** (Admin Say):
|
||||
- Admin coordination
|
||||
- Discussing player behavior
|
||||
- Planning admin actions
|
||||
- Private admin discussions
|
||||
|
||||
**css_cssay** (Colored Say):
|
||||
- Important server announcements
|
||||
- Event notifications
|
||||
- Eye-catching messages
|
||||
- Server information
|
||||
|
||||
**css_say** (Say):
|
||||
- General admin announcements
|
||||
- Rule reminders
|
||||
- Warnings to all players
|
||||
- Admin presence
|
||||
|
||||
**css_psay** (Private Say):
|
||||
- Private warnings
|
||||
- Individual help
|
||||
- Direct player communication
|
||||
- Discretion needed
|
||||
|
||||
**css_csay** (Center Say):
|
||||
- Emergency announcements
|
||||
- Cannot-miss messages
|
||||
- Round starts/events
|
||||
- Countdowns
|
||||
|
||||
**css_hsay** (HUD Say):
|
||||
- Persistent information
|
||||
- Less urgent announcements
|
||||
- Server info
|
||||
- Website/Discord links
|
||||
|
||||
---
|
||||
|
||||
## Communication Guidelines
|
||||
|
||||
### Professional Communication
|
||||
|
||||
1. **Be clear and concise** - Don't spam long messages
|
||||
2. **Use appropriate command** - Don't center-spam trivial messages
|
||||
3. **Check for typos** - You represent the server
|
||||
4. **Avoid excessive colors** - Can be hard to read
|
||||
|
||||
### Spam Prevention
|
||||
|
||||
1. **Don't overuse center say** - Very intrusive for players
|
||||
2. **Space out announcements** - Don't flood chat
|
||||
3. **Use HUD say for persistent info** - Less annoying
|
||||
4. **Coordinate with other admins** - Avoid duplicate messages
|
||||
|
||||
### Effective Messaging
|
||||
|
||||
**Good Examples:**
|
||||
```bash
|
||||
css_cssay "🎯 New map voting system available! Type !mapvote"
|
||||
css_psay PlayerName "Hey! Please enable your mic or use team chat"
|
||||
css_asay "Checking player demos for possible aimbot"
|
||||
```
|
||||
|
||||
**Poor Examples:**
|
||||
```bash
|
||||
css_csay "hi" # Don't use center say for trivial messages
|
||||
css_cssay "a" "b" "c" "d" # Don't spam center messages
|
||||
css_say "asdfasdfasdf" # Unprofessional
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Silent Mode
|
||||
|
||||
Admins can use silent mode to hide their activity:
|
||||
|
||||
```bash
|
||||
css_hide # Toggle silent mode
|
||||
```
|
||||
|
||||
When in silent mode:
|
||||
- Chat messages still work
|
||||
- Admin name might be hidden (depends on config)
|
||||
- Useful for undercover moderation
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Show Activity Type
|
||||
|
||||
Controls how admin actions are displayed:
|
||||
|
||||
```json
|
||||
"ShowActivityType": 2
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `0` - Hide all admin activity
|
||||
- `1` - Anonymous ("An admin says...")
|
||||
- `2` - Show name ("AdminName says...")
|
||||
|
||||
---
|
||||
|
||||
## Chat Restrictions
|
||||
|
||||
### Respecting Gags
|
||||
|
||||
Remember that:
|
||||
- `css_asay` works even if admin is gagged (admin chat)
|
||||
- Other commands respect communication penalties
|
||||
- Can't use chat commands while silenced
|
||||
|
||||
### Permission Requirements
|
||||
|
||||
All chat commands require `@css/chat` permission:
|
||||
|
||||
```bash
|
||||
css_addadmin STEAMID "Name" "@css/chat" 50 0
|
||||
```
|
||||
|
||||
Or add to a group with chat permission:
|
||||
```bash
|
||||
css_addgroup "#moderators" "@css/chat,@css/kick" 50
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Messages not showing
|
||||
|
||||
**Check:**
|
||||
- Do you have `@css/chat` permission?
|
||||
- Are you silenced/gagged?
|
||||
- Check console for errors
|
||||
- Verify player is connected (for css_psay)
|
||||
|
||||
### Colors not working
|
||||
|
||||
**Check:**
|
||||
- Use correct color code syntax: `{red}`
|
||||
- Some commands may not support colors
|
||||
- Different chat systems handle colors differently
|
||||
|
||||
### Players can't see center/HUD messages
|
||||
|
||||
**Check:**
|
||||
- CS2 client-side chat settings
|
||||
- Conflicting HUD plugins
|
||||
- Server console for errors
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- **[Communication Commands](basecomms)** - Gag, mute, silence players
|
||||
- **[Base Commands](basecommands)** - Admin management
|
||||
- **[Ban Commands](basebans)** - Player punishment
|
||||
626
CS2-SimpleAdmin-docs/docs/user/commands/basecommands.md
Normal file
@@ -0,0 +1,626 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Base Commands
|
||||
|
||||
Core admin commands for server management and admin system.
|
||||
|
||||
## Player Information
|
||||
|
||||
### Show Penalties
|
||||
|
||||
View your own active penalties.
|
||||
|
||||
```bash
|
||||
css_penalties
|
||||
css_mypenalties
|
||||
css_comms
|
||||
```
|
||||
|
||||
**Permission:** None (all players)
|
||||
|
||||
**Shows:**
|
||||
- Active bans
|
||||
- Active communication restrictions (gag, mute, silence)
|
||||
- Warning count
|
||||
- Duration remaining
|
||||
|
||||
---
|
||||
|
||||
### Hide Penalty Notifications
|
||||
|
||||
Hide penalty notifications when you connect to the server.
|
||||
|
||||
```bash
|
||||
css_hidecomms
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Notes:**
|
||||
- Toggle on/off
|
||||
- Admins won't see penalty notifications on join
|
||||
- Useful for admin privacy
|
||||
|
||||
---
|
||||
|
||||
## Admin Menu
|
||||
|
||||
### Open Admin Menu
|
||||
|
||||
Opens the main admin menu interface.
|
||||
|
||||
```bash
|
||||
css_admin
|
||||
```
|
||||
|
||||
**Permission:** `@css/generic`
|
||||
|
||||
**Features:**
|
||||
- Player management
|
||||
- Server management
|
||||
- Ban/kick/mute players via menu
|
||||
- Map changing
|
||||
- Custom server commands
|
||||
|
||||
---
|
||||
|
||||
### Admin Help
|
||||
|
||||
Print the admin help file.
|
||||
|
||||
```bash
|
||||
css_adminhelp
|
||||
```
|
||||
|
||||
**Permission:** `@css/generic`
|
||||
|
||||
**Shows:**
|
||||
- Available commands for your permission level
|
||||
- Command syntax
|
||||
- Permission requirements
|
||||
|
||||
---
|
||||
|
||||
## Admin Management
|
||||
|
||||
### Add Admin
|
||||
|
||||
Add a new admin to the database.
|
||||
|
||||
```bash
|
||||
css_addadmin <steamid> <name> <flags/groups> <immunity> <duration>
|
||||
```
|
||||
|
||||
**Permission:** `@css/root`
|
||||
|
||||
**Parameters:**
|
||||
- `steamid` - Player's SteamID (any format)
|
||||
- `name` - Admin name (for identification)
|
||||
- `flags/groups` - Permission flags or group name
|
||||
- `immunity` - Immunity level (0-100, higher = more protection)
|
||||
- `duration` - Duration in minutes (0 = permanent)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Add permanent admin with root access
|
||||
css_addadmin 76561198012345678 "AdminName" "@css/root" 99 0
|
||||
|
||||
# Add moderator for 30 days
|
||||
css_addadmin STEAM_1:0:12345678 "ModName" "@css/kick,@css/ban" 50 43200
|
||||
|
||||
# Add admin using group
|
||||
css_addadmin 76561198012345678 "AdminName" "#moderators" 60 0
|
||||
|
||||
# Add admin to all servers (-g flag)
|
||||
css_addadmin 76561198012345678 "AdminName" "@css/root" 99 0 -g
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
- `-g` - Add to all servers (global admin)
|
||||
|
||||
---
|
||||
|
||||
### Delete Admin
|
||||
|
||||
Remove an admin from the database.
|
||||
|
||||
```bash
|
||||
css_deladmin <steamid>
|
||||
```
|
||||
|
||||
**Permission:** `@css/root`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Remove admin from current server
|
||||
css_deladmin 76561198012345678
|
||||
|
||||
# Remove admin from all servers (-g flag)
|
||||
css_deladmin 76561198012345678 -g
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Add Admin Group
|
||||
|
||||
Create a new admin group.
|
||||
|
||||
```bash
|
||||
css_addgroup <group_name> <flags> <immunity>
|
||||
```
|
||||
|
||||
**Permission:** `@css/root`
|
||||
|
||||
**Parameters:**
|
||||
- `group_name` - Name of the group (e.g., "#moderators")
|
||||
- `flags` - Permission flags for the group
|
||||
- `immunity` - Default immunity level for group members
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Create moderator group
|
||||
css_addgroup "#moderators" "@css/kick,@css/ban,@css/chat" 50
|
||||
|
||||
# Create VIP group
|
||||
css_addgroup "#vip" "@css/vip" 10
|
||||
|
||||
# Create global group (-g flag)
|
||||
css_addgroup "#owner" "@css/root" 99 -g
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
- `-g` - Create group on all servers
|
||||
|
||||
---
|
||||
|
||||
### Delete Admin Group
|
||||
|
||||
Remove an admin group from the database.
|
||||
|
||||
```bash
|
||||
css_delgroup <group_name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/root`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Delete group from current server
|
||||
css_delgroup "#moderators"
|
||||
|
||||
# Delete group from all servers (-g flag)
|
||||
css_delgroup "#moderators" -g
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Reload Admins
|
||||
|
||||
Reload admin permissions from the database.
|
||||
|
||||
```bash
|
||||
css_reloadadmins
|
||||
```
|
||||
|
||||
**Permission:** `@css/root`
|
||||
|
||||
**When to use:**
|
||||
- After adding/removing admins via database
|
||||
- After modifying admin permissions
|
||||
- After group changes
|
||||
- Troubleshooting permission issues
|
||||
|
||||
**Note:** Admins are automatically reloaded periodically and on map change (if configured).
|
||||
|
||||
---
|
||||
|
||||
## Player Information
|
||||
|
||||
### Hide in Scoreboard
|
||||
|
||||
Toggle admin stealth mode (hide from scoreboard).
|
||||
|
||||
```bash
|
||||
css_hide
|
||||
css_stealth
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Features:**
|
||||
- Hides you from the scoreboard
|
||||
- Makes admin actions anonymous
|
||||
- Useful for undercover moderation
|
||||
|
||||
---
|
||||
|
||||
### Who is This Player
|
||||
|
||||
Show detailed information about a player.
|
||||
|
||||
```bash
|
||||
css_who <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/generic`
|
||||
|
||||
**Shows:**
|
||||
- Player name and SteamID
|
||||
- IP address (if you have `@css/showip` permission)
|
||||
- Connection time
|
||||
- Active penalties
|
||||
- Warning count
|
||||
- Ban history
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_who #123
|
||||
css_who PlayerName
|
||||
css_who @me
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Show Disconnected Players
|
||||
|
||||
Show recently disconnected players.
|
||||
|
||||
```bash
|
||||
css_disconnected
|
||||
css_last
|
||||
css_last10 # Show last 10 (config value)
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Shows:**
|
||||
- Player name
|
||||
- SteamID
|
||||
- Disconnect time
|
||||
- Disconnect reason
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
"DisconnectedPlayersHistoryCount": 10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Show Warns for Player
|
||||
|
||||
Open warn list for a specific player.
|
||||
|
||||
```bash
|
||||
css_warns <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Shows:**
|
||||
- All warnings for the player
|
||||
- Warning reasons
|
||||
- Admins who issued warnings
|
||||
- Warning timestamps
|
||||
- Total warning count
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_warns #123
|
||||
css_warns PlayerName
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Show Online Players
|
||||
|
||||
Show information about all online players.
|
||||
|
||||
```bash
|
||||
css_players
|
||||
```
|
||||
|
||||
**Permission:** `@css/generic`
|
||||
|
||||
**Shows:**
|
||||
- List of all connected players
|
||||
- UserIDs
|
||||
- Names
|
||||
- Teams
|
||||
- Connection status
|
||||
|
||||
---
|
||||
|
||||
## Server Management
|
||||
|
||||
### Kick Player
|
||||
|
||||
Kick a player from the server.
|
||||
|
||||
```bash
|
||||
css_kick <#userid or name> [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_kick #123 "AFK"
|
||||
css_kick PlayerName "Rule violation"
|
||||
css_kick @spec "Cleaning spectators"
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
"KickTime": 5
|
||||
```
|
||||
Delay in seconds before kicking (allows player to see the reason).
|
||||
|
||||
---
|
||||
|
||||
### Change Map
|
||||
|
||||
Change to a different map.
|
||||
|
||||
```bash
|
||||
css_map <mapname>
|
||||
css_changemap <mapname>
|
||||
```
|
||||
|
||||
**Permission:** `@css/changemap`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_map de_dust2
|
||||
css_changemap de_mirage
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
"DefaultMaps": [
|
||||
"de_dust2",
|
||||
"de_mirage",
|
||||
"de_inferno"
|
||||
]
|
||||
```
|
||||
|
||||
Maps in this list appear in the map change menu.
|
||||
|
||||
---
|
||||
|
||||
### Change Workshop Map
|
||||
|
||||
Change to a workshop map by ID or name.
|
||||
|
||||
```bash
|
||||
css_wsmap <name or id>
|
||||
css_changewsmap <name or id>
|
||||
css_workshop <name or id>
|
||||
```
|
||||
|
||||
**Permission:** `@css/changemap`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_wsmap 123456789
|
||||
css_wsmap aim_map
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
"WorkshopMaps": {
|
||||
"aim_map": "123456789",
|
||||
"surf_map": "987654321"
|
||||
}
|
||||
```
|
||||
|
||||
Maps configured here can be changed by name instead of ID.
|
||||
|
||||
---
|
||||
|
||||
### Change CVar
|
||||
|
||||
Change a server console variable.
|
||||
|
||||
```bash
|
||||
css_cvar <cvar> <value>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cvar`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_cvar sv_cheats 1
|
||||
css_cvar mp_roundtime 5
|
||||
css_cvar mp_maxmoney 16000
|
||||
```
|
||||
|
||||
**Warning:** This is a powerful command. Only grant to trusted admins.
|
||||
|
||||
---
|
||||
|
||||
### Execute RCON Command
|
||||
|
||||
Execute any command as the server.
|
||||
|
||||
```bash
|
||||
css_rcon <command>
|
||||
```
|
||||
|
||||
**Permission:** `@css/rcon`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_rcon status
|
||||
css_rcon changelevel de_dust2
|
||||
css_rcon sv_cheats 1
|
||||
```
|
||||
|
||||
**Warning:** Extremely powerful command. Only grant to server owners.
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
"DisableDangerousCommands": true
|
||||
```
|
||||
|
||||
When enabled, prevents execution of dangerous commands via css_rcon.
|
||||
|
||||
---
|
||||
|
||||
### Restart Game
|
||||
|
||||
Restart the current game/round.
|
||||
|
||||
```bash
|
||||
css_rr
|
||||
css_rg
|
||||
css_restart
|
||||
css_restartgame
|
||||
```
|
||||
|
||||
**Permission:** `@css/generic`
|
||||
|
||||
**Notes:**
|
||||
- Restarts the current round
|
||||
- Score is reset
|
||||
- Players remain connected
|
||||
|
||||
---
|
||||
|
||||
## Permission Flags
|
||||
|
||||
Common permission flags used in CS2-SimpleAdmin:
|
||||
|
||||
| Flag | Description | Common Use |
|
||||
|------|-------------|------------|
|
||||
| `@css/generic` | Generic admin access | Basic admin menu, info commands |
|
||||
| `@css/chat` | Chat management | Gag, mute, silence |
|
||||
| `@css/kick` | Kick players | Kick, warnings, player info |
|
||||
| `@css/ban` | Ban players | Ban, banip, addban |
|
||||
| `@css/unban` | Unban players | Remove bans |
|
||||
| `@css/permban` | Permanent bans | Issue permanent bans |
|
||||
| `@css/changemap` | Change maps | Map changing |
|
||||
| `@css/cvar` | Change cvars | Server variable modification |
|
||||
| `@css/rcon` | Execute rcon | Full server control |
|
||||
| `@css/root` | Root access | All permissions, admin management |
|
||||
| `@css/slay` | Slay/respawn | Player manipulation |
|
||||
| `@css/cheats` | Cheat commands | God mode, noclip, give weapons |
|
||||
| `@css/showip` | View IPs | See player IP addresses |
|
||||
|
||||
---
|
||||
|
||||
## Immunity System
|
||||
|
||||
Immunity prevents lower-level admins from targeting higher-level admins.
|
||||
|
||||
**How it works:**
|
||||
- Each admin has an immunity value (0-100)
|
||||
- Higher immunity = more protection
|
||||
- Admins can only target players with lower immunity
|
||||
|
||||
**Example:**
|
||||
- Admin A has immunity 50
|
||||
- Admin B has immunity 30
|
||||
- Admin A can ban Admin B
|
||||
- Admin B cannot ban Admin A
|
||||
|
||||
**Best Practice:**
|
||||
- Owner: 99
|
||||
- Senior admins: 80-90
|
||||
- Moderators: 50-70
|
||||
- Trial mods: 20-40
|
||||
- Regular players: 0
|
||||
|
||||
---
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Reload Admins on Map Change
|
||||
|
||||
```json
|
||||
"ReloadAdminsEveryMapChange": false
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `true` - Reload admin permissions every map change
|
||||
- `false` - Only reload when explicitly requested (better performance)
|
||||
|
||||
### Show Activity Type
|
||||
|
||||
```json
|
||||
"ShowActivityType": 2
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `0` - Hide all admin activity
|
||||
- `1` - Show activity anonymously ("An admin banned PlayerName")
|
||||
- `2` - Show admin name ("AdminName banned PlayerName")
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Admin Management
|
||||
|
||||
1. **Use groups** - Easier to manage than individual permissions
|
||||
2. **Set appropriate immunity** - Prevent abuse
|
||||
3. **Time-limited admin** - For trial moderators
|
||||
4. **Document changes** - Keep track of who has what permissions
|
||||
|
||||
### Permission Assignment
|
||||
|
||||
**Recommended hierarchy:**
|
||||
```
|
||||
Root (@css/root, immunity 99):
|
||||
- Server owners only
|
||||
|
||||
Senior Admin (@css/ban,@css/kick,@css/chat,@css/changemap, immunity 80):
|
||||
- Trusted long-term admins
|
||||
|
||||
Moderator (@css/kick,@css/chat, immunity 50):
|
||||
- Regular moderators
|
||||
|
||||
Trial Mod (@css/kick, immunity 20):
|
||||
- New moderators on probation
|
||||
```
|
||||
|
||||
### Security
|
||||
|
||||
1. **Limit @css/rcon** - Only to server owner
|
||||
2. **Limit @css/cvar** - Only to senior admins
|
||||
3. **Monitor admin actions** - Review logs regularly
|
||||
4. **Use time-limited admin** - For temporary staff
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Admin permissions not working
|
||||
|
||||
**Check:**
|
||||
1. Is admin correctly added with `css_addadmin`?
|
||||
2. Run `css_reloadadmins`
|
||||
3. Check database connection
|
||||
4. Verify SteamID format
|
||||
|
||||
### Can't target another admin
|
||||
|
||||
**Check:**
|
||||
- Your immunity level vs target's immunity
|
||||
- You need equal or higher immunity to target
|
||||
|
||||
### Commands not available
|
||||
|
||||
**Check:**
|
||||
- Your permission flags
|
||||
- Commands.json for disabled commands
|
||||
- Server console for errors
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- **[Ban Commands](basebans)** - Player punishment
|
||||
- **[Communication Commands](basecomms)** - Chat/voice management
|
||||
- **[Player Commands](playercommands)** - Player manipulation
|
||||
396
CS2-SimpleAdmin-docs/docs/user/commands/basecomms.md
Normal file
@@ -0,0 +1,396 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Communication Commands
|
||||
|
||||
Commands for managing player communication (voice and text chat).
|
||||
|
||||
## Overview
|
||||
|
||||
CS2-SimpleAdmin provides three types of communication restrictions:
|
||||
|
||||
- **Gag** - Blocks text chat only
|
||||
- **Mute** - Blocks voice chat only
|
||||
- **Silence** - Blocks both text and voice chat
|
||||
|
||||
---
|
||||
|
||||
## Gag Commands
|
||||
|
||||
### Gag Player
|
||||
|
||||
Prevent a player from using text chat.
|
||||
|
||||
```bash
|
||||
css_gag <#userid or name> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_gag #123 30 "Chat spam"
|
||||
css_gag PlayerName 1440 "Advertising"
|
||||
css_gag @all 5 "Everyone quiet for 5 minutes"
|
||||
```
|
||||
|
||||
### Add Gag (Offline Player)
|
||||
|
||||
Gag a player by SteamID even if they're offline.
|
||||
|
||||
```bash
|
||||
css_addgag <steamid> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_addgag 76561198012345678 60 "Chat abuse"
|
||||
css_addgag STEAM_1:0:12345678 1440 "Spam"
|
||||
```
|
||||
|
||||
### Ungag Player
|
||||
|
||||
Remove a gag from a player.
|
||||
|
||||
```bash
|
||||
css_ungag <steamid or name> [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_ungag PlayerName "Appeal accepted"
|
||||
css_ungag 76561198012345678 "Mistake"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mute Commands
|
||||
|
||||
### Mute Player
|
||||
|
||||
Prevent a player from using voice chat.
|
||||
|
||||
```bash
|
||||
css_mute <#userid or name> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_mute #123 30 "Mic spam"
|
||||
css_mute PlayerName 60 "Loud music"
|
||||
css_mute @t 5 "T team timeout"
|
||||
```
|
||||
|
||||
### Add Mute (Offline Player)
|
||||
|
||||
Mute a player by SteamID even if they're offline.
|
||||
|
||||
```bash
|
||||
css_addmute <steamid> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_addmute 76561198012345678 120 "Voice abuse"
|
||||
css_addmute STEAM_1:0:12345678 1440 "Mic spam"
|
||||
```
|
||||
|
||||
### Unmute Player
|
||||
|
||||
Remove a mute from a player.
|
||||
|
||||
```bash
|
||||
css_unmute <steamid or name> [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_unmute PlayerName "Behavior improved"
|
||||
css_unmute 76561198012345678 "Time served"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Silence Commands
|
||||
|
||||
### Silence Player
|
||||
|
||||
Block both text and voice chat from a player.
|
||||
|
||||
```bash
|
||||
css_silence <#userid or name> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_silence #123 60 "Complete communication ban"
|
||||
css_silence PlayerName 1440 "Severe abuse"
|
||||
```
|
||||
|
||||
### Add Silence (Offline Player)
|
||||
|
||||
Silence a player by SteamID even if they're offline.
|
||||
|
||||
```bash
|
||||
css_addsilence <steamid> [time in minutes/0 perm] [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_addsilence 76561198012345678 120 "Total communication ban"
|
||||
css_addsilence STEAM_1:0:12345678 0 "Permanent silence"
|
||||
```
|
||||
|
||||
### Unsilence Player
|
||||
|
||||
Remove a silence from a player.
|
||||
|
||||
```bash
|
||||
css_unsilence <steamid or name> [reason]
|
||||
```
|
||||
|
||||
**Permission:** `@css/chat`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_unsilence PlayerName "Punishment complete"
|
||||
css_unsilence 76561198012345678 "Appeal granted"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Permission Requirements
|
||||
|
||||
All communication commands require the `@css/chat` permission.
|
||||
|
||||
| Command | Action | Offline Support |
|
||||
|---------|--------|----------------|
|
||||
| `css_gag` | Block text chat | No |
|
||||
| `css_addgag` | Block text chat | Yes |
|
||||
| `css_ungag` | Remove text block | Yes |
|
||||
| `css_mute` | Block voice chat | No |
|
||||
| `css_addmute` | Block voice chat | Yes |
|
||||
| `css_unmute` | Remove voice block | Yes |
|
||||
| `css_silence` | Block both | No |
|
||||
| `css_addsilence` | Block both | Yes |
|
||||
| `css_unsilence` | Remove both blocks | Yes |
|
||||
|
||||
---
|
||||
|
||||
## Communication Penalty Types
|
||||
|
||||
### When to Use Each Type
|
||||
|
||||
**Gag (Text Only):**
|
||||
- Chat spam
|
||||
- Advertising in chat
|
||||
- Offensive messages
|
||||
- Spectator camera abuse messages
|
||||
|
||||
**Mute (Voice Only):**
|
||||
- Mic spam
|
||||
- Loud music/noise
|
||||
- Voice abuse
|
||||
- Excessive talking
|
||||
|
||||
**Silence (Both):**
|
||||
- Severe abuse cases
|
||||
- Players who switch between chat and voice to evade
|
||||
- Complete communication bans
|
||||
|
||||
---
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### UserMessage Gag Type
|
||||
|
||||
In `CS2-SimpleAdmin.json`:
|
||||
|
||||
```json
|
||||
"UserMessageGagChatType": false
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `false` - Standard gag implementation (default)
|
||||
- `true` - Alternative gag using UserMessage system
|
||||
|
||||
**Note:** Try switching this if gag commands don't work as expected.
|
||||
|
||||
### Notify Penalties on Connect
|
||||
|
||||
```json
|
||||
"NotifyPenaltiesToAdminOnConnect": true
|
||||
```
|
||||
|
||||
When enabled, admins see active communication penalties when they join:
|
||||
```
|
||||
[CS2-SimpleAdmin] PlayerName is gagged (30 minutes remaining)
|
||||
[CS2-SimpleAdmin] PlayerName is muted (1 hour remaining)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checking Penalties
|
||||
|
||||
### View Own Penalties
|
||||
|
||||
Players can check their own communication penalties:
|
||||
|
||||
```bash
|
||||
css_penalties
|
||||
css_mypenalties
|
||||
css_comms
|
||||
```
|
||||
|
||||
Shows:
|
||||
- Active gags, mutes, and silences
|
||||
- Duration remaining
|
||||
- Reason for penalty
|
||||
- Admin who issued it
|
||||
|
||||
### Admin View of Penalties
|
||||
|
||||
Use the admin menu or player info command:
|
||||
|
||||
```bash
|
||||
css_who <#userid or name>
|
||||
```
|
||||
|
||||
Shows complete penalty history including communication restrictions.
|
||||
|
||||
---
|
||||
|
||||
## Time Durations
|
||||
|
||||
Common duration values:
|
||||
|
||||
| Duration | Minutes | Use Case |
|
||||
|----------|---------|----------|
|
||||
| 1 minute | 1 | Quick warning |
|
||||
| 5 minutes | 5 | Minor spam |
|
||||
| 15 minutes | 15 | Standard timeout |
|
||||
| 30 minutes | 30 | Repeated offense |
|
||||
| 1 hour | 60 | Moderate abuse |
|
||||
| 6 hours | 360 | Serious abuse |
|
||||
| 1 day | 1440 | Severe abuse |
|
||||
| 1 week | 10080 | Extreme cases |
|
||||
| Permanent | 0 | Reserved for worst cases |
|
||||
|
||||
---
|
||||
|
||||
## Player Targeting
|
||||
|
||||
All communication commands support advanced targeting:
|
||||
|
||||
- `@all` - Target all players
|
||||
- `@ct` - Target all Counter-Terrorists
|
||||
- `@t` - Target all Terrorists
|
||||
- `@spec` - Target all spectators
|
||||
- `#123` - Target by userid
|
||||
- `PlayerName` - Target by name
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_gag @all 1 "Quiet for one minute"
|
||||
css_mute @t 5 "T team voice timeout"
|
||||
css_silence @ct 10 "CT team complete silence"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Communication Management
|
||||
|
||||
1. **Start with warnings** - Not all chat issues need immediate gag
|
||||
2. **Use appropriate durations** - Match severity to punishment
|
||||
3. **Provide reasons** - Helps players understand what they did wrong
|
||||
4. **Consider silence carefully** - Complete communication ban is harsh
|
||||
|
||||
### Gag vs Mute vs Silence
|
||||
|
||||
**Progressive Approach:**
|
||||
1. Verbal warning
|
||||
2. Gag or mute (specific to offense)
|
||||
3. Longer gag/mute for repeat offense
|
||||
4. Silence for continued abuse
|
||||
5. Temporary ban for extreme cases
|
||||
|
||||
### Documentation
|
||||
|
||||
1. **Always provide reasons** - Required for appeals
|
||||
2. **Be specific** - "Mic spam" not just "abuse"
|
||||
3. **Keep records** - Use admin logs for repeat offenders
|
||||
|
||||
---
|
||||
|
||||
## Discord Integration
|
||||
|
||||
Communication penalties can send Discord notifications when configured:
|
||||
|
||||
```json
|
||||
"DiscordPenaltyGagSettings": [...],
|
||||
"DiscordPenaltyMuteSettings": [...],
|
||||
"DiscordPenaltySilenceSettings": [...]
|
||||
```
|
||||
|
||||
Notifications include:
|
||||
- Player name and SteamID
|
||||
- Penalty type and duration
|
||||
- Reason provided
|
||||
- Admin who issued it
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Gag doesn't work
|
||||
|
||||
**Try:**
|
||||
1. Switch `UserMessageGagChatType` in config
|
||||
2. Ensure player is actually gagged (check with `css_who`)
|
||||
3. Check for conflicting plugins
|
||||
|
||||
### Mute doesn't block voice
|
||||
|
||||
**Check:**
|
||||
- Is sv_talk_enemy_dead configured correctly?
|
||||
- Are there voice management plugins conflicting?
|
||||
- Check server console for errors
|
||||
|
||||
### Penalties not persistent across maps
|
||||
|
||||
**Solution:**
|
||||
- Penalties should persist automatically
|
||||
- Check database connection
|
||||
- Verify MultiServerMode if using multiple servers
|
||||
|
||||
### Player can't see their penalties
|
||||
|
||||
**Check:**
|
||||
- Command aliases in Commands.json
|
||||
- Ensure `css_penalties` is enabled
|
||||
- Check player chat permissions
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- **[Ban Commands](basebans)** - For more serious offenses
|
||||
- **[Player Commands](playercommands)** - Kick, team switch
|
||||
- **[Base Commands](basecommands)** - Admin management
|
||||
436
CS2-SimpleAdmin-docs/docs/user/commands/basevotes.md
Normal file
@@ -0,0 +1,436 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Vote Commands
|
||||
|
||||
Commands for creating polls and votes on your server.
|
||||
|
||||
## Create Vote
|
||||
|
||||
Create a custom poll for players to vote on.
|
||||
|
||||
```bash
|
||||
css_vote <question> [option1] [option2] [option3] ...
|
||||
```
|
||||
|
||||
**Permission:** `@css/generic`
|
||||
|
||||
**Parameters:**
|
||||
- `question` - The question to ask players
|
||||
- `option1, option2, ...` - Vote options (at least 2 required)
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple Yes/No Vote
|
||||
|
||||
```bash
|
||||
css_vote "Should we change map?" "Yes" "No"
|
||||
```
|
||||
|
||||
**Player sees:**
|
||||
```
|
||||
Vote: Should we change map?
|
||||
1. Yes
|
||||
2. No
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Multiple Options
|
||||
|
||||
```bash
|
||||
css_vote "Which map should we play next?" "de_dust2" "de_mirage" "de_inferno" "de_nuke"
|
||||
```
|
||||
|
||||
**Player sees:**
|
||||
```
|
||||
Vote: Which map should we play next?
|
||||
1. de_dust2
|
||||
2. de_mirage
|
||||
3. de_inferno
|
||||
4. de_nuke
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Rule Vote
|
||||
|
||||
```bash
|
||||
css_vote "Should we allow AWPs?" "Yes" "No" "Only one per team"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Activity Vote
|
||||
|
||||
```bash
|
||||
css_vote "What should we do?" "Surf" "Deathrun" "Competitive" "Fun Round"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How Voting Works
|
||||
|
||||
### Player Participation
|
||||
|
||||
Players vote by:
|
||||
1. Opening their chat
|
||||
2. Typing the number of their choice
|
||||
3. Or using vote menu (if available)
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Player: 1 (votes for option 1)
|
||||
Player: 2 (votes for option 2)
|
||||
```
|
||||
|
||||
### Vote Duration
|
||||
|
||||
- Default vote time: ~30 seconds
|
||||
- Vote timer shows on screen
|
||||
- Results shown when vote ends
|
||||
|
||||
### Vote Results
|
||||
|
||||
After voting ends, results are displayed:
|
||||
```
|
||||
Vote Results:
|
||||
1. Yes - 12 votes (60%)
|
||||
2. No - 8 votes (40%)
|
||||
|
||||
Winner: Yes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Map Voting
|
||||
|
||||
```bash
|
||||
css_vote "Next map?" "de_dust2" "de_mirage" "de_inferno"
|
||||
```
|
||||
|
||||
### Rule Changes
|
||||
|
||||
```bash
|
||||
css_vote "Enable friendly fire?" "Yes" "No"
|
||||
css_vote "Restart round?" "Yes" "No"
|
||||
```
|
||||
|
||||
### Player Punishment
|
||||
|
||||
```bash
|
||||
css_vote "Ban PlayerName for cheating?" "Yes" "No"
|
||||
css_vote "Kick AFK player?" "Yes" "No"
|
||||
```
|
||||
|
||||
### Fun Rounds
|
||||
|
||||
```bash
|
||||
css_vote "Fun round type?" "Knife only" "Deagle only" "Zeus only" "Normal"
|
||||
```
|
||||
|
||||
### Server Settings
|
||||
|
||||
```bash
|
||||
css_vote "Round time?" "2 minutes" "3 minutes" "5 minutes"
|
||||
css_vote "Max players?" "10v10" "5v5" "7v7"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Question Clarity
|
||||
|
||||
**Good Questions:**
|
||||
- Clear and concise
|
||||
- Specific
|
||||
- Easy to understand
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
✅ css_vote "Change to de_dust2?" "Yes" "No"
|
||||
❌ css_vote "Map?" "Yes" "No" # Unclear what map
|
||||
|
||||
✅ css_vote "Restart this round?" "Yes" "No"
|
||||
❌ css_vote "Restart?" "Yes" "No" # Restart what?
|
||||
```
|
||||
|
||||
### Option Limits
|
||||
|
||||
**Recommendations:**
|
||||
- 2-5 options ideal
|
||||
- Too many options confuse players
|
||||
- Keep options brief
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
✅ css_vote "Next map?" "dust2" "mirage" "inferno"
|
||||
❌ css_vote "Next map?" "de_dust2" "de_mirage" "de_inferno" "de_nuke" "de_vertigo" "de_ancient" "de_anubis"
|
||||
```
|
||||
|
||||
### Timing
|
||||
|
||||
**When to use votes:**
|
||||
- End of round
|
||||
- Between maps
|
||||
- During downtime
|
||||
- Not during active gameplay
|
||||
|
||||
**When NOT to use votes:**
|
||||
- Mid-round
|
||||
- During clutch situations
|
||||
- Too frequently
|
||||
|
||||
### Vote Spam Prevention
|
||||
|
||||
Don't spam votes:
|
||||
```bash
|
||||
❌ Multiple votes in quick succession
|
||||
❌ Overlapping votes
|
||||
❌ Votes every round
|
||||
```
|
||||
|
||||
Wait for current vote to finish before starting another.
|
||||
|
||||
---
|
||||
|
||||
## Vote Types
|
||||
|
||||
### Administrative Votes
|
||||
|
||||
**Map change:**
|
||||
```bash
|
||||
css_vote "Change map now?" "Yes" "No"
|
||||
```
|
||||
|
||||
**Server restart:**
|
||||
```bash
|
||||
css_vote "Restart server?" "Yes" "No"
|
||||
```
|
||||
|
||||
**Rule enforcement:**
|
||||
```bash
|
||||
css_vote "Kick PlayerName?" "Yes" "No"
|
||||
```
|
||||
|
||||
### Gameplay Votes
|
||||
|
||||
**Weapon restrictions:**
|
||||
```bash
|
||||
css_vote "Disable AWP?" "Yes" "No"
|
||||
```
|
||||
|
||||
**Team scramble:**
|
||||
```bash
|
||||
css_vote "Scramble teams?" "Yes" "No"
|
||||
```
|
||||
|
||||
**Round rules:**
|
||||
```bash
|
||||
css_vote "Knife round first?" "Yes" "No"
|
||||
```
|
||||
|
||||
### Event Votes
|
||||
|
||||
**Tournament:**
|
||||
```bash
|
||||
css_vote "Start tournament?" "Yes, start now" "Wait 5 minutes" "No, cancel"
|
||||
```
|
||||
|
||||
**Custom game mode:**
|
||||
```bash
|
||||
css_vote "Game mode?" "Hide and Seek" "Gungame" "Surf" "Normal"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Limitations
|
||||
|
||||
### Technical Limits
|
||||
|
||||
- Maximum ~10 options (depends on menu system)
|
||||
- One vote at a time
|
||||
- Requires active players to participate
|
||||
|
||||
### Permission Required
|
||||
|
||||
Only admins with `@css/generic` permission can start votes.
|
||||
|
||||
To grant permission:
|
||||
```bash
|
||||
css_addadmin STEAMID "Name" "@css/generic" 50 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vote Results Handling
|
||||
|
||||
### Manual Enforcement
|
||||
|
||||
Votes don't automatically execute actions. Admins must:
|
||||
|
||||
1. **See the results**
|
||||
2. **Manually execute the winning option**
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Start vote
|
||||
css_vote "Change to de_dust2?" "Yes" "No"
|
||||
|
||||
# If "Yes" wins, manually change map
|
||||
css_map de_dust2
|
||||
```
|
||||
|
||||
### Why Manual?
|
||||
|
||||
- Prevents abuse
|
||||
- Allows admin oversight
|
||||
- Gives control over execution
|
||||
|
||||
---
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Combining with Commands
|
||||
|
||||
Use votes to decide, then execute:
|
||||
|
||||
```bash
|
||||
# Vote on map
|
||||
css_vote "Next map?" "dust2" "mirage" "inferno"
|
||||
# If dust2 wins:
|
||||
css_map de_dust2
|
||||
|
||||
# Vote on player kick
|
||||
css_vote "Kick PlayerName?" "Yes" "No"
|
||||
# If Yes wins:
|
||||
css_kick PlayerName "Voted to be kicked"
|
||||
```
|
||||
|
||||
### Sequential Votes
|
||||
|
||||
Run multiple votes for complex decisions:
|
||||
|
||||
```bash
|
||||
# First vote: Mode
|
||||
css_vote "Game mode?" "Competitive" "Casual"
|
||||
|
||||
# If Competitive wins, second vote:
|
||||
css_vote "Round time?" "2 min" "3 min" "5 min"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Check if vote commands are enabled in:
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/Commands.json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"Commands": {
|
||||
"css_vote": {
|
||||
"Aliases": [
|
||||
"css_vote"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To disable votes, remove all aliases:
|
||||
```json
|
||||
{
|
||||
"Commands": {
|
||||
"css_vote": {
|
||||
"Aliases": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Vote doesn't start
|
||||
|
||||
**Check:**
|
||||
- Do you have `@css/generic` permission?
|
||||
- Is command enabled in Commands.json?
|
||||
- Are there at least 2 options?
|
||||
|
||||
### Players can't vote
|
||||
|
||||
**Check:**
|
||||
- Vote menu is showing
|
||||
- Players know how to vote (type number in chat)
|
||||
- Vote hasn't already ended
|
||||
|
||||
### Vote results not showing
|
||||
|
||||
**Check:**
|
||||
- Wait for vote to complete
|
||||
- Check server console
|
||||
- Ensure voting system is working
|
||||
|
||||
---
|
||||
|
||||
## Permission Requirements
|
||||
|
||||
| Command | Permission | Description |
|
||||
|---------|------------|-------------|
|
||||
| `css_vote` | `@css/generic` | Create votes/polls |
|
||||
|
||||
---
|
||||
|
||||
## Tips
|
||||
|
||||
### Effective Polling
|
||||
|
||||
1. **Ask clear questions** - No ambiguity
|
||||
2. **Limit options** - 2-4 is ideal
|
||||
3. **Time it right** - Between rounds
|
||||
4. **Follow through** - Execute winning option
|
||||
5. **Don't overuse** - Votes lose impact if spammed
|
||||
|
||||
### Community Engagement
|
||||
|
||||
Use votes to:
|
||||
- Involve community in decisions
|
||||
- Gauge player preferences
|
||||
- Create democratic server atmosphere
|
||||
- Get feedback on changes
|
||||
|
||||
### Example Scenarios
|
||||
|
||||
**New map test:**
|
||||
```bash
|
||||
css_vote "Try new map cs_office?" "Yes" "No"
|
||||
```
|
||||
|
||||
**Event planning:**
|
||||
```bash
|
||||
css_vote "Tournament this weekend?" "Saturday" "Sunday" "No thanks"
|
||||
```
|
||||
|
||||
**Rule feedback:**
|
||||
```bash
|
||||
css_vote "Keep no-AWP rule?" "Yes" "No" "Only limit to 2"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- **[Base Commands](basecommands)** - Server management
|
||||
- **[Chat Commands](basechat)** - Announcements
|
||||
- **[Player Commands](playercommands)** - Player actions
|
||||
516
CS2-SimpleAdmin-docs/docs/user/commands/playercommands.md
Normal file
@@ -0,0 +1,516 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Player Commands
|
||||
|
||||
Commands for managing and manipulating players on your server.
|
||||
|
||||
:::note
|
||||
Many of these commands are included in the base plugin. For extended fun commands (god mode, noclip, freeze, etc.), see the [Fun Commands Module](../../modules/funcommands).
|
||||
:::
|
||||
|
||||
## Player Management
|
||||
|
||||
### Slay Player
|
||||
|
||||
Kill a player instantly.
|
||||
|
||||
```bash
|
||||
css_slay <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_slay #123
|
||||
css_slay PlayerName
|
||||
css_slay @ct # Slay all CTs
|
||||
css_slay @t # Slay all terrorists
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Punishment for rule breaking
|
||||
- Ending rounds quickly
|
||||
- Removing camping players
|
||||
|
||||
---
|
||||
|
||||
### Slap Player
|
||||
|
||||
Slap a player, dealing damage and pushing them.
|
||||
|
||||
```bash
|
||||
css_slap <#userid or name> [damage]
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `damage` - HP damage to deal (default: 0)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_slap #123 # Slap with no damage
|
||||
css_slap PlayerName 10 # Slap for 10 HP damage
|
||||
css_slap @all 5 # Slap everyone for 5 damage
|
||||
```
|
||||
|
||||
**Effects:**
|
||||
- Player is pushed in a random direction
|
||||
- Optional damage dealt
|
||||
- Makes slap sound
|
||||
|
||||
**Use cases:**
|
||||
- Funny punishment
|
||||
- Getting player attention
|
||||
- Moving AFK players
|
||||
|
||||
---
|
||||
|
||||
## Player Attributes
|
||||
|
||||
### Set Player Health
|
||||
|
||||
Set a player's health points.
|
||||
|
||||
```bash
|
||||
css_hp <#userid or name> <health>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_hp #123 100 # Set to full health
|
||||
css_hp PlayerName 1 # Set to 1 HP
|
||||
css_hp @ct 200 # Give all CTs 200 HP
|
||||
```
|
||||
|
||||
**Valid range:** 1 - 999+
|
||||
|
||||
---
|
||||
|
||||
### Set Player Speed
|
||||
|
||||
Modify a player's movement speed.
|
||||
|
||||
```bash
|
||||
css_speed <#userid or name> <speed>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `speed` - Speed multiplier (1.0 = normal, 2.0 = double speed, 0.5 = half speed)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_speed #123 1.5 # 150% speed
|
||||
css_speed PlayerName 0.5 # 50% speed (slow motion)
|
||||
css_speed @all 2.0 # Double speed for everyone
|
||||
css_speed #123 1.0 # Reset to normal speed
|
||||
```
|
||||
|
||||
**Common values:**
|
||||
- `0.5` - Slow motion
|
||||
- `1.0` - Normal (default)
|
||||
- `1.5` - Fast
|
||||
- `2.0` - Very fast
|
||||
- `3.0` - Extremely fast
|
||||
|
||||
**Note:** Speed persists across respawns until reset.
|
||||
|
||||
---
|
||||
|
||||
### Set Player Gravity
|
||||
|
||||
Modify a player's gravity.
|
||||
|
||||
```bash
|
||||
css_gravity <#userid or name> <gravity>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Parameters:**
|
||||
- `gravity` - Gravity multiplier (1.0 = normal, 0.5 = moon jump, 2.0 = heavy)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_gravity #123 0.5 # Moon jump
|
||||
css_gravity PlayerName 2.0 # Heavy gravity
|
||||
css_gravity @all 0.1 # Super jump for everyone
|
||||
css_gravity #123 1.0 # Reset to normal
|
||||
```
|
||||
|
||||
**Common values:**
|
||||
- `0.1` - Super high jumps
|
||||
- `0.5` - Moon gravity
|
||||
- `1.0` - Normal (default)
|
||||
- `2.0` - Heavy/fast falling
|
||||
|
||||
**Note:** Gravity persists across respawns until reset.
|
||||
|
||||
---
|
||||
|
||||
### Set Player Money
|
||||
|
||||
Set a player's money amount.
|
||||
|
||||
```bash
|
||||
css_money <#userid or name> <amount>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_money #123 16000 # Max money
|
||||
css_money PlayerName 0 # Remove all money
|
||||
css_money @ct 10000 # Give all CTs $10,000
|
||||
```
|
||||
|
||||
**Valid range:** 0 - 65535 (CS2 engine limit)
|
||||
|
||||
---
|
||||
|
||||
## Team Management
|
||||
|
||||
### Switch Player Team
|
||||
|
||||
Move a player to a different team.
|
||||
|
||||
```bash
|
||||
css_team <#userid or name> [ct/t/spec] [-k]
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Parameters:**
|
||||
- `ct` - Counter-Terrorist team
|
||||
- `t` - Terrorist team
|
||||
- `spec` - Spectators
|
||||
- `-k` - Kill player during switch (optional)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_team #123 ct # Move to CT
|
||||
css_team PlayerName t # Move to T
|
||||
css_team @spec t # Move all spectators to T
|
||||
css_team #123 ct -k # Move to CT and kill
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
"TeamSwitchType": 1
|
||||
```
|
||||
|
||||
Determines team switch behavior.
|
||||
|
||||
---
|
||||
|
||||
### Rename Player
|
||||
|
||||
Temporarily rename a player.
|
||||
|
||||
```bash
|
||||
css_rename <#userid or name> <new name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_rename #123 "NewName"
|
||||
css_rename PlayerName "RenamedPlayer"
|
||||
css_rename @all "Everyone"
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Rename is temporary (resets on reconnect)
|
||||
- For permanent rename, use `css_prename`
|
||||
|
||||
---
|
||||
|
||||
### Permanent Rename
|
||||
|
||||
Permanently force a player's name.
|
||||
|
||||
```bash
|
||||
css_prename <#userid or name> <new name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/ban`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_prename #123 "EnforcedName"
|
||||
css_prename PlayerName "NewIdentity"
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Name is enforced even after reconnect
|
||||
- Stored in database
|
||||
- Player cannot change it
|
||||
- Useful for offensive names
|
||||
|
||||
---
|
||||
|
||||
## Weapon Management
|
||||
|
||||
### Give Weapon
|
||||
|
||||
Give a weapon to a player.
|
||||
|
||||
```bash
|
||||
css_give <#userid or name> <weapon>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cheats`
|
||||
|
||||
**Weapon names:**
|
||||
- Rifles: `ak47`, `m4a1`, `m4a1_silencer`, `aug`, `sg556`, `awp`
|
||||
- SMGs: `mp5sd`, `mp7`, `mp9`, `p90`, `ump45`
|
||||
- Heavy: `nova`, `xm1014`, `mag7`, `m249`, `negev`
|
||||
- Pistols: `deagle`, `elite`, `fiveseven`, `glock`, `hkp2000`, `p250`, `tec9`, `usp_silencer`
|
||||
- Grenades: `flashbang`, `hegrenade`, `smokegrenade`, `molotov`, `incgrenade`, `decoy`
|
||||
- Equipment: `kevlar`, `assaultsuit`, `defuser`, `knife`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_give #123 awp
|
||||
css_give PlayerName ak47
|
||||
css_give @ct m4a1
|
||||
css_give @all deagle
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Strip Weapons
|
||||
|
||||
Remove all weapons from a player.
|
||||
|
||||
```bash
|
||||
css_strip <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/slay`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_strip #123
|
||||
css_strip PlayerName
|
||||
css_strip @t # Disarm all terrorists
|
||||
```
|
||||
|
||||
**Effects:**
|
||||
- Removes all weapons
|
||||
- Leaves player with knife only
|
||||
- Removes grenades and equipment
|
||||
|
||||
---
|
||||
|
||||
## Teleportation
|
||||
|
||||
### Teleport to Player
|
||||
|
||||
Teleport yourself to another player.
|
||||
|
||||
```bash
|
||||
css_tp <#userid or name>
|
||||
css_tpto <#userid or name>
|
||||
css_goto <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_tp #123
|
||||
css_goto PlayerName
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Checking player behavior
|
||||
- Admin help
|
||||
- Spectating suspicious players
|
||||
|
||||
---
|
||||
|
||||
### Teleport Player to You
|
||||
|
||||
Bring a player to your location.
|
||||
|
||||
```bash
|
||||
css_bring <#userid or name>
|
||||
css_tphere <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/kick`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_bring #123
|
||||
css_tphere PlayerName
|
||||
css_bring @all # Bring everyone to you
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Moving stuck players
|
||||
- Gathering players
|
||||
- Admin events
|
||||
|
||||
---
|
||||
|
||||
### Respawn Player
|
||||
|
||||
Respawn a dead player.
|
||||
|
||||
```bash
|
||||
css_respawn <#userid or name>
|
||||
```
|
||||
|
||||
**Permission:** `@css/cheats`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
css_respawn #123
|
||||
css_respawn PlayerName
|
||||
css_respawn @ct # Respawn all dead CTs
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Player spawns at spawn point
|
||||
- Equipped with default weapons
|
||||
- Can break competitive balance
|
||||
|
||||
---
|
||||
|
||||
## Player Targeting
|
||||
|
||||
All player commands support advanced targeting:
|
||||
|
||||
### Target Syntax
|
||||
|
||||
- `@all` - All players
|
||||
- `@ct` - All Counter-Terrorists
|
||||
- `@t` - All Terrorists
|
||||
- `@spec` - All spectators
|
||||
- `@alive` - All alive players
|
||||
- `@dead` - All dead players
|
||||
- `@bot` - All bots
|
||||
- `@human` - All human players
|
||||
- `@me` - Yourself
|
||||
- `#123` - Specific user ID
|
||||
- `PlayerName` - By name (partial match supported)
|
||||
|
||||
### Multiple Targets
|
||||
|
||||
```bash
|
||||
css_slay @ct # Kills all CTs
|
||||
css_hp @all 200 # Give everyone 200 HP
|
||||
css_speed @t 2.0 # Make all Ts fast
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Permission Requirements
|
||||
|
||||
| Command | Required Permission | Description |
|
||||
|---------|-------------------|-------------|
|
||||
| `css_slay` | `@css/slay` | Kill players |
|
||||
| `css_slap` | `@css/slay` | Slap players |
|
||||
| `css_hp` | `@css/slay` | Set health |
|
||||
| `css_speed` | `@css/slay` | Modify speed |
|
||||
| `css_gravity` | `@css/slay` | Modify gravity |
|
||||
| `css_money` | `@css/slay` | Set money |
|
||||
| `css_team` | `@css/kick` | Change team |
|
||||
| `css_rename` | `@css/kick` | Temporary rename |
|
||||
| `css_prename` | `@css/ban` | Permanent rename |
|
||||
| `css_give` | `@css/cheats` | Give weapons |
|
||||
| `css_strip` | `@css/slay` | Remove weapons |
|
||||
| `css_tp` | `@css/kick` | Teleport to player |
|
||||
| `css_bring` | `@css/kick` | Bring player |
|
||||
| `css_respawn` | `@css/cheats` | Respawn players |
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Punishment Commands
|
||||
|
||||
**Slay:**
|
||||
- Use for rule violations
|
||||
- Better than kick for minor issues
|
||||
- Allows player to stay and learn
|
||||
|
||||
**Slap:**
|
||||
- Lighter punishment
|
||||
- Good for warnings
|
||||
- Can be funny/entertaining
|
||||
|
||||
### Gameplay Modification
|
||||
|
||||
**HP/Speed/Gravity:**
|
||||
- Use for events/fun rounds
|
||||
- Don't abuse during competitive play
|
||||
- Reset to normal after use
|
||||
|
||||
**Respawn:**
|
||||
- Very disruptive to gameplay
|
||||
- Use sparingly
|
||||
- Good for fixing bugs/mistakes
|
||||
|
||||
### Team Management
|
||||
|
||||
**Team switching:**
|
||||
- Balance teams fairly
|
||||
- Don't abuse for winning
|
||||
- Use `-k` flag for competitive integrity
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Team Switch Behavior
|
||||
|
||||
```json
|
||||
"TeamSwitchType": 1
|
||||
```
|
||||
|
||||
Controls how team switching works.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Speed/Gravity not persisting
|
||||
|
||||
**Solution:** These are maintained by a timer. If they reset:
|
||||
- Check server console for errors
|
||||
- Ensure plugin is loaded correctly
|
||||
- Try reapplying the modification
|
||||
|
||||
### Can't teleport
|
||||
|
||||
**Check:**
|
||||
- Target player is connected
|
||||
- You have correct permissions
|
||||
- Both players are valid
|
||||
|
||||
### Give weapon not working
|
||||
|
||||
**Check:**
|
||||
- Weapon name is correct
|
||||
- Player is alive
|
||||
- Player has inventory space
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- **[Fun Commands Module](../../modules/funcommands)** - Extended fun commands (freeze, god mode, noclip)
|
||||
- **[Ban Commands](basebans)** - Punishment commands
|
||||
- **[Base Commands](basecommands)** - Server management
|
||||
397
CS2-SimpleAdmin-docs/docs/user/configuration.md
Normal file
@@ -0,0 +1,397 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Configuration
|
||||
|
||||
Learn how to configure CS2-SimpleAdmin to suit your server's needs.
|
||||
|
||||
## Configuration File Location
|
||||
|
||||
The main configuration file is located at:
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
|
||||
```
|
||||
|
||||
## Configuration Structure
|
||||
|
||||
The configuration file is divided into several sections:
|
||||
|
||||
### Database Configuration
|
||||
|
||||
Configure your database connection:
|
||||
|
||||
```json
|
||||
"DatabaseConfig": {
|
||||
"DatabaseType": "SQLite",
|
||||
"SqliteFilePath": "cs2-simpleadmin.sqlite",
|
||||
"DatabaseHost": "",
|
||||
"DatabasePort": 3306,
|
||||
"DatabaseUser": "",
|
||||
"DatabasePassword": "",
|
||||
"DatabaseName": "",
|
||||
"DatabaseSSlMode": "preferred"
|
||||
}
|
||||
```
|
||||
|
||||
**Database Types:**
|
||||
- `SQLite` - Local database file (good for single server)
|
||||
- `MySQL` - MySQL/MariaDB server (required for multi-server setups)
|
||||
|
||||
**MySQL Example:**
|
||||
```json
|
||||
"DatabaseConfig": {
|
||||
"DatabaseType": "MySQL",
|
||||
"DatabaseHost": "localhost",
|
||||
"DatabasePort": 3306,
|
||||
"DatabaseUser": "cs2admin",
|
||||
"DatabasePassword": "your_password",
|
||||
"DatabaseName": "cs2_simpleadmin",
|
||||
"DatabaseSSlMode": "preferred"
|
||||
}
|
||||
```
|
||||
|
||||
### Other Settings
|
||||
|
||||
General plugin settings:
|
||||
|
||||
```json
|
||||
"OtherSettings": {
|
||||
"ShowActivityType": 2,
|
||||
"TeamSwitchType": 1,
|
||||
"KickTime": 5,
|
||||
"BanType": 1,
|
||||
"TimeMode": 1,
|
||||
"DisableDangerousCommands": true,
|
||||
"MaxBanDuration": 10080,
|
||||
"MaxMuteDuration": 10080,
|
||||
"ExpireOldIpBans": 0,
|
||||
"ReloadAdminsEveryMapChange": false,
|
||||
"DisconnectedPlayersHistoryCount": 10,
|
||||
"NotifyPenaltiesToAdminOnConnect": true,
|
||||
"ShowBanMenuIfNoTime": true,
|
||||
"UserMessageGagChatType": false,
|
||||
"CheckMultiAccountsByIp": true,
|
||||
"AdditionalCommandsToLog": [],
|
||||
"IgnoredIps": []
|
||||
}
|
||||
```
|
||||
|
||||
**Settings Explained:**
|
||||
|
||||
| Setting | Description | Default |
|
||||
|---------|-------------|---------|
|
||||
| `ShowActivityType` | How to display admin actions (0=hide, 1=anonymous, 2=show name) | 2 |
|
||||
| `TeamSwitchType` | Team switch behavior | 1 |
|
||||
| `KickTime` | Delay before kicking player (seconds) | 5 |
|
||||
| `BanType` | Ban type (1=SteamID, 2=IP, 3=Both) | 1 |
|
||||
| `TimeMode` | Time display mode | 1 |
|
||||
| `DisableDangerousCommands` | Disable potentially dangerous commands | true |
|
||||
| `MaxBanDuration` | Maximum ban duration in minutes (0=unlimited) | 10080 |
|
||||
| `MaxMuteDuration` | Maximum mute duration in minutes (0=unlimited) | 10080 |
|
||||
| `ExpireOldIpBans` | Auto-expire IP bans after X days (0=disabled) | 0 |
|
||||
| `ReloadAdminsEveryMapChange` | Reload admin permissions on map change | false |
|
||||
| `DisconnectedPlayersHistoryCount` | Number of disconnected players to track | 10 |
|
||||
| `NotifyPenaltiesToAdminOnConnect` | Show penalties to admins when they connect | true |
|
||||
| `ShowBanMenuIfNoTime` | Show ban menu even without time parameter | true |
|
||||
| `UserMessageGagChatType` | Use UserMessage for gag (alternative chat blocking) | false |
|
||||
| `CheckMultiAccountsByIp` | Detect multiple accounts from same IP | true |
|
||||
| `AdditionalCommandsToLog` | Array of additional commands to log | [] |
|
||||
| `IgnoredIps` | IPs to ignore in multi-account detection | [] |
|
||||
|
||||
### Metrics and Updates
|
||||
|
||||
```json
|
||||
"EnableMetrics": true,
|
||||
"EnableUpdateCheck": true
|
||||
```
|
||||
|
||||
- `EnableMetrics` - Send anonymous usage statistics
|
||||
- `EnableUpdateCheck` - Check for plugin updates on load
|
||||
|
||||
### Timezone
|
||||
|
||||
Set your server's timezone for accurate timestamps:
|
||||
|
||||
```json
|
||||
"Timezone": "UTC"
|
||||
```
|
||||
|
||||
See the [list of timezones](#timezone-list) below.
|
||||
|
||||
### Warning Thresholds
|
||||
|
||||
Configure automatic actions when players reach warning thresholds:
|
||||
|
||||
```json
|
||||
"WarnThreshold": {
|
||||
"998": "css_addban STEAMID64 60 \"3/4 Warn\"",
|
||||
"999": "css_ban #USERID 120 \"4/4 Warn\""
|
||||
}
|
||||
```
|
||||
|
||||
**Example:** Automatically ban a player for 60 minutes when they receive their 3rd warning.
|
||||
|
||||
### Multi-Server Mode
|
||||
|
||||
Enable if you're running multiple servers with a shared database:
|
||||
|
||||
```json
|
||||
"MultiServerMode": true
|
||||
```
|
||||
|
||||
When enabled:
|
||||
- Bans are shared across all servers
|
||||
- Admin permissions can be global or server-specific
|
||||
- Player data is synchronized
|
||||
|
||||
### Discord Integration
|
||||
|
||||
Send notifications to Discord webhooks:
|
||||
|
||||
```json
|
||||
"Discord": {
|
||||
"DiscordLogWebhook": "https://discord.com/api/webhooks/...",
|
||||
"DiscordPenaltyBanSettings": [...],
|
||||
"DiscordPenaltyMuteSettings": [...],
|
||||
"DiscordPenaltyGagSettings": [...],
|
||||
"DiscordPenaltySilenceSettings": [...],
|
||||
"DiscordPenaltyWarnSettings": [...],
|
||||
"DiscordAssociatedAccountsSettings": [...]
|
||||
}
|
||||
```
|
||||
|
||||
**Webhook Settings:**
|
||||
Each penalty type can have its own webhook configuration:
|
||||
|
||||
```json
|
||||
"DiscordPenaltyBanSettings": [
|
||||
{
|
||||
"name": "Color",
|
||||
"value": "#FF0000"
|
||||
},
|
||||
{
|
||||
"name": "Webhook",
|
||||
"value": "https://discord.com/api/webhooks/YOUR_WEBHOOK_HERE"
|
||||
},
|
||||
{
|
||||
"name": "ThumbnailUrl",
|
||||
"value": "https://example.com/ban-icon.png"
|
||||
},
|
||||
{
|
||||
"name": "ImageUrl",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "Footer",
|
||||
"value": "CS2-SimpleAdmin"
|
||||
},
|
||||
{
|
||||
"name": "Time",
|
||||
"value": "{relative}"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Available Placeholders:**
|
||||
- `{relative}` - Relative timestamp
|
||||
- `{fixed}` - Fixed timestamp
|
||||
|
||||
### Map Configuration
|
||||
|
||||
Configure default maps and workshop maps:
|
||||
|
||||
```json
|
||||
"DefaultMaps": [
|
||||
"de_dust2",
|
||||
"de_mirage",
|
||||
"de_inferno"
|
||||
],
|
||||
"WorkshopMaps": {
|
||||
"aim_map": "123456789",
|
||||
"surf_map": "987654321"
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Server Commands
|
||||
|
||||
Add custom commands to the admin menu:
|
||||
|
||||
```json
|
||||
"CustomServerCommands": [
|
||||
{
|
||||
"Flag": "@css/root",
|
||||
"DisplayName": "Reload Admins",
|
||||
"Command": "css_reloadadmins"
|
||||
},
|
||||
{
|
||||
"Flag": "@css/cheats",
|
||||
"DisplayName": "Enable sv_cheats",
|
||||
"Command": "sv_cheats 1"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Menu Configuration
|
||||
|
||||
Configure menu appearance and options:
|
||||
|
||||
```json
|
||||
"MenuConfig": {
|
||||
"MenuType": "selectable",
|
||||
"Durations": [
|
||||
{ "name": "1 minute", "duration": 1 },
|
||||
{ "name": "5 minutes", "duration": 5 },
|
||||
{ "name": "15 minutes", "duration": 15 },
|
||||
{ "name": "1 hour", "duration": 60 },
|
||||
{ "name": "1 day", "duration": 1440 },
|
||||
{ "name": "7 days", "duration": 10080 },
|
||||
{ "name": "14 days", "duration": 20160 },
|
||||
{ "name": "30 days", "duration": 43200 },
|
||||
{ "name": "Permanent", "duration": 0 }
|
||||
],
|
||||
"BanReasons": [
|
||||
"Hacking",
|
||||
"Voice Abuse",
|
||||
"Chat Abuse",
|
||||
"Admin disrespect",
|
||||
"Other"
|
||||
],
|
||||
"KickReasons": [
|
||||
"Voice Abuse",
|
||||
"Chat Abuse",
|
||||
"Admin disrespect",
|
||||
"Other"
|
||||
],
|
||||
"WarnReasons": [
|
||||
"Voice Abuse",
|
||||
"Chat Abuse",
|
||||
"Admin disrespect",
|
||||
"Other"
|
||||
],
|
||||
"MuteReasons": [
|
||||
"Advertising",
|
||||
"Spamming",
|
||||
"Spectator camera abuse",
|
||||
"Hate",
|
||||
"Admin disrespect",
|
||||
"Other"
|
||||
],
|
||||
"AdminFlags": [
|
||||
{ "name": "Generic", "flag": "@css/generic" },
|
||||
{ "name": "Chat", "flag": "@css/chat" },
|
||||
{ "name": "Change Map", "flag": "@css/changemap" },
|
||||
{ "name": "Slay", "flag": "@css/slay" },
|
||||
{ "name": "Kick", "flag": "@css/kick" },
|
||||
{ "name": "Ban", "flag": "@css/ban" },
|
||||
{ "name": "Perm Ban", "flag": "@css/permban" },
|
||||
{ "name": "Unban", "flag": "@css/unban" },
|
||||
{ "name": "Show IP", "flag": "@css/showip" },
|
||||
{ "name": "Cvar", "flag": "@css/cvar" },
|
||||
{ "name": "Rcon", "flag": "@css/rcon" },
|
||||
{ "name": "Root (all flags)", "flag": "@css/root" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Timezone List
|
||||
|
||||
<details>
|
||||
<summary>Click to expand timezone list</summary>
|
||||
|
||||
```
|
||||
UTC
|
||||
America/New_York
|
||||
America/Chicago
|
||||
America/Denver
|
||||
America/Los_Angeles
|
||||
Europe/London
|
||||
Europe/Paris
|
||||
Europe/Berlin
|
||||
Europe/Warsaw
|
||||
Europe/Moscow
|
||||
Asia/Tokyo
|
||||
Asia/Shanghai
|
||||
Asia/Dubai
|
||||
Australia/Sydney
|
||||
Pacific/Auckland
|
||||
... (and many more)
|
||||
```
|
||||
|
||||
For a complete list, see the info.txt file in the documentation folder.
|
||||
|
||||
</details>
|
||||
|
||||
## Commands Configuration
|
||||
|
||||
You can customize command aliases in:
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/Commands.json
|
||||
```
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"Commands": {
|
||||
"css_ban": {
|
||||
"Aliases": [
|
||||
"css_ban",
|
||||
"css_ban2"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to:
|
||||
- **Disable commands** - Remove all aliases from the array
|
||||
- **Add aliases** - Add multiple command variations
|
||||
- **Rename commands** - Change the command name while keeping functionality
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Security
|
||||
|
||||
1. **Use MySQL in production** - SQLite is not suitable for multi-server setups
|
||||
2. **Set MaxBanDuration** - Prevent accidental permanent bans
|
||||
3. **Enable DisableDangerousCommands** - Protect against accidental server crashes
|
||||
4. **Use strong database passwords** - If using MySQL
|
||||
|
||||
### Performance
|
||||
|
||||
1. **Set ReloadAdminsEveryMapChange to false** - Unless you frequently modify admin permissions
|
||||
2. **Limit DisconnectedPlayersHistoryCount** - Reduce memory usage
|
||||
3. **Use database indices** - Migrations create these automatically
|
||||
|
||||
### Multi-Server Setup
|
||||
|
||||
1. **Enable MultiServerMode** - Share data across servers
|
||||
2. **Use MySQL** - Required for multi-server
|
||||
3. **Configure server IDs** - Each server gets a unique ID automatically
|
||||
4. **Test penalties** - Ensure bans work across all servers
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Changes not taking effect
|
||||
|
||||
**Solution:** Reload the plugin or restart the server:
|
||||
```
|
||||
css_plugins reload CS2-SimpleAdmin
|
||||
```
|
||||
|
||||
### Discord webhooks not working
|
||||
|
||||
**Solution:**
|
||||
- Verify webhook URL is correct
|
||||
- Check that the webhook is not deleted in Discord
|
||||
- Ensure server has internet access
|
||||
|
||||
### TimeMode issues
|
||||
|
||||
**Solution:** Set your timezone correctly in the configuration
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Learn admin commands](commands/basebans)** - Browse available commands
|
||||
- **[Set up admins](#)** - Add your admin team
|
||||
- **[Configure modules](../modules/intro)** - Extend functionality
|
||||
188
CS2-SimpleAdmin-docs/docs/user/installation.md
Normal file
@@ -0,0 +1,188 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Installation
|
||||
|
||||
This guide will help you install CS2-SimpleAdmin on your Counter-Strike 2 server.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before installing CS2-SimpleAdmin, ensure you have the following dependencies installed:
|
||||
|
||||
### Required Dependencies
|
||||
|
||||
1. **[CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp/)** (v1.0.340+)
|
||||
- The core framework for CS2 server plugins
|
||||
|
||||
2. **[AnyBaseLibCS2](https://github.com/NickFox007/AnyBaseLibCS2)**
|
||||
- Required by PlayerSettings
|
||||
|
||||
3. **[PlayerSettings](https://github.com/NickFox007/PlayerSettingsCS2)**
|
||||
- Required by MenuManager
|
||||
|
||||
4. **[MenuManagerCS2](https://github.com/NickFox007/MenuManagerCS2)**
|
||||
- Provides the menu system
|
||||
|
||||
### Database Requirements
|
||||
|
||||
You'll need either:
|
||||
- **MySQL** server (recommended for production)
|
||||
- **SQLite** (built-in, good for testing)
|
||||
|
||||
## Installation Steps
|
||||
|
||||
### 1. Download the Plugin
|
||||
|
||||
Download the latest release from the [GitHub Releases page](https://github.com/daffyyyy/CS2-SimpleAdmin/releases).
|
||||
|
||||
You can either:
|
||||
- Download the pre-built release ZIP file
|
||||
- Clone the repository and build from source
|
||||
|
||||
### 2. Extract Files
|
||||
|
||||
Extract the downloaded files to your server's CounterStrikeSharp directory:
|
||||
|
||||
```
|
||||
game/csgo/addons/counterstrikesharp/plugins/CS2-SimpleAdmin/
|
||||
```
|
||||
|
||||
Your directory structure should look like this:
|
||||
|
||||
```
|
||||
csgo/
|
||||
└── addons/
|
||||
└── counterstrikesharp/
|
||||
├── plugins/
|
||||
│ └── CS2-SimpleAdmin/
|
||||
│ ├── CS2-SimpleAdmin.dll
|
||||
│ ├── lang/
|
||||
│ └── ... (other files)
|
||||
└── shared/
|
||||
└── CS2-SimpleAdminApi/
|
||||
└── CS2-SimpleAdminApi.dll
|
||||
```
|
||||
|
||||
### 3. First Launch
|
||||
|
||||
Start your server. On the first launch, CS2-SimpleAdmin will:
|
||||
|
||||
1. Create a configuration file at:
|
||||
```
|
||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
|
||||
```
|
||||
|
||||
2. Create a database (if using SQLite):
|
||||
```
|
||||
addons/counterstrikesharp/plugins/CS2-SimpleAdmin/cs2-simpleadmin.sqlite
|
||||
```
|
||||
|
||||
3. Apply database migrations automatically
|
||||
|
||||
### 4. Configure the Plugin
|
||||
|
||||
Edit the generated configuration file to match your server setup.
|
||||
|
||||
See the [Configuration Guide](configuration) for detailed information.
|
||||
|
||||
### 5. Restart Your Server
|
||||
|
||||
After editing the configuration, restart your server or reload the plugin:
|
||||
|
||||
```bash
|
||||
css_plugins reload CS2-SimpleAdmin
|
||||
```
|
||||
|
||||
## Building from Source
|
||||
|
||||
If you want to build CS2-SimpleAdmin from source:
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- .NET 8.0 SDK
|
||||
- Git
|
||||
|
||||
### Build Steps
|
||||
|
||||
1. **Clone the repository:**
|
||||
```bash
|
||||
git clone https://github.com/daffyyyy/CS2-SimpleAdmin.git
|
||||
cd CS2-SimpleAdmin
|
||||
```
|
||||
|
||||
2. **Restore dependencies:**
|
||||
```bash
|
||||
dotnet restore CS2-SimpleAdmin.sln
|
||||
```
|
||||
|
||||
3. **Build the solution:**
|
||||
```bash
|
||||
dotnet build CS2-SimpleAdmin.sln -c Release
|
||||
```
|
||||
|
||||
4. **Build output location:**
|
||||
```
|
||||
CS2-SimpleAdmin/bin/Release/net8.0/
|
||||
CS2-SimpleAdminApi/bin/Release/net8.0/
|
||||
```
|
||||
|
||||
5. **Copy to server:**
|
||||
- Copy `CS2-SimpleAdmin.dll` and its dependencies to `plugins/CS2-SimpleAdmin/`
|
||||
- Copy `CS2-SimpleAdminApi.dll` to `shared/CS2-SimpleAdminApi/`
|
||||
|
||||
## Verification
|
||||
|
||||
To verify the installation was successful:
|
||||
|
||||
1. **Check server console** for the plugin load message:
|
||||
```
|
||||
[CS2-SimpleAdmin] Plugin loaded successfully
|
||||
```
|
||||
|
||||
2. **Run an admin command** in-game:
|
||||
```
|
||||
css_admin
|
||||
```
|
||||
|
||||
3. **Check the logs** at:
|
||||
```
|
||||
addons/counterstrikesharp/logs/CS2-SimpleAdmin*.txt
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin doesn't load
|
||||
|
||||
**Solution:** Ensure all required dependencies are installed:
|
||||
- CounterStrikeSharp (latest version)
|
||||
- AnyBaseLibCS2
|
||||
- PlayerSettings
|
||||
- MenuManagerCS2
|
||||
|
||||
### Database connection errors
|
||||
|
||||
**Solution:**
|
||||
- For MySQL: Verify database credentials in the config file
|
||||
- For SQLite: Ensure the plugin has write permissions in its directory
|
||||
|
||||
### Commands not working
|
||||
|
||||
**Solution:**
|
||||
- Check that you have admin permissions configured
|
||||
- Verify the commands are enabled in `Commands.json`
|
||||
- Check server console for error messages
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Configure your plugin](configuration)** - Set up database, permissions, and features
|
||||
- **[Learn the commands](commands/basebans)** - Browse available admin commands
|
||||
- **[Add admins](#)** - Set up your admin team
|
||||
|
||||
## Need Help?
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. Check the [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues) for similar problems
|
||||
2. Review server logs for error messages
|
||||
3. Ask for help on [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
||||
81
CS2-SimpleAdmin-docs/docs/user/intro.md
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
Welcome to **CS2-SimpleAdmin** - a comprehensive administration plugin for Counter-Strike 2 servers built with C# (.NET 8.0) for CounterStrikeSharp.
|
||||
|
||||
## What is CS2-SimpleAdmin?
|
||||
|
||||
CS2-SimpleAdmin is a powerful server administration tool that provides comprehensive features for managing your Counter-Strike 2 server. It offers:
|
||||
|
||||
- **Player Management** - Ban, kick, mute, gag, and warn players
|
||||
- **Admin System** - Flexible permission system with flags and groups
|
||||
- **Multi-Server Support** - Manage multiple servers with a shared database
|
||||
- **Discord Integration** - Send notifications to Discord webhooks
|
||||
- **Menu System** - Easy-to-use admin menus
|
||||
- **Extensive Commands** - Over 50 admin commands
|
||||
- **Module Support** - Extend functionality with custom modules
|
||||
|
||||
## Key Features
|
||||
|
||||
### 🛡️ Comprehensive Penalty System
|
||||
- **Bans** - Ban players by SteamID or IP address
|
||||
- **Mutes** - Mute voice communication
|
||||
- **Gags** - Gag text chat
|
||||
- **Silence** - Block both voice and text
|
||||
- **Warnings** - Progressive warning system with auto-escalation
|
||||
|
||||
### 👥 Flexible Admin System
|
||||
- Permission-based access control using flags
|
||||
- Admin groups for easy management
|
||||
- Immunity levels to prevent abuse
|
||||
- Server-specific or global admin assignments
|
||||
|
||||
### 🗄️ Database Support
|
||||
- **MySQL** - For production environments
|
||||
- **SQLite** - For quick setup and testing
|
||||
- Automatic migration system
|
||||
- Multi-server mode with shared data
|
||||
|
||||
### 🎮 User-Friendly Interface
|
||||
- Interactive admin menus
|
||||
- In-game admin panel
|
||||
- Player selection menus
|
||||
- Duration and reason selection
|
||||
|
||||
### 🔧 Extensibility
|
||||
- Public API for module development
|
||||
- Event system for custom integrations
|
||||
- Command registration system
|
||||
- Menu builder API
|
||||
|
||||
## Requirements
|
||||
|
||||
Before installing CS2-SimpleAdmin, make sure you have:
|
||||
|
||||
- [CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp/) (v1.0.340+)
|
||||
- [AnyBaseLibCS2](https://github.com/NickFox007/AnyBaseLibCS2)
|
||||
- [PlayerSettings](https://github.com/NickFox007/PlayerSettingsCS2)
|
||||
- [MenuManagerCS2](https://github.com/NickFox007/MenuManagerCS2)
|
||||
- MySQL database (or use SQLite for testing)
|
||||
|
||||
## Quick Links
|
||||
|
||||
- **[Installation Guide](installation)** - Get started with CS2-SimpleAdmin
|
||||
- **[Configuration](configuration)** - Learn how to configure the plugin
|
||||
- **[Commands](commands/basebans)** - Browse all available commands
|
||||
- **[GitHub Repository](https://github.com/daffyyyy/CS2-SimpleAdmin)** - Source code and releases
|
||||
|
||||
## Community & Support
|
||||
|
||||
Need help or want to contribute?
|
||||
|
||||
- **Issues** - Report bugs on [GitHub Issues](https://github.com/daffyyyy/CS2-SimpleAdmin/issues)
|
||||
- **Discussions** - Ask questions on [GitHub Discussions](https://github.com/daffyyyy/CS2-SimpleAdmin/discussions)
|
||||
- **Pull Requests** - Contribute improvements
|
||||
|
||||
## License
|
||||
|
||||
CS2-SimpleAdmin is open-source software. Check the [GitHub repository](https://github.com/daffyyyy/CS2-SimpleAdmin) for license details.
|
||||
174
CS2-SimpleAdmin-docs/docusaurus.config.js
Normal file
@@ -0,0 +1,174 @@
|
||||
// @ts-check
|
||||
// `@type` JSDoc annotations allow editor autocompletion and type checking
|
||||
// (when paired with `@ts-check`).
|
||||
// There are various equivalent ways to declare your Docusaurus config.
|
||||
// See: https://docusaurus.io/docs/api/docusaurus-config
|
||||
|
||||
import {themes as prismThemes} from 'prism-react-renderer';
|
||||
|
||||
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: 'CS2-SimpleAdmin',
|
||||
tagline: 'Comprehensive administration plugin for Counter-Strike 2 servers',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
// Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future
|
||||
future: {
|
||||
v4: true, // Improve compatibility with the upcoming Docusaurus v4
|
||||
},
|
||||
|
||||
// Set the production url of your site here
|
||||
url: 'https://cs2-simpleadmin.daffyy.dev',
|
||||
// Set the /<baseUrl>/ pathname under which your site is served
|
||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||
baseUrl: '/',
|
||||
|
||||
// GitHub pages deployment config.
|
||||
// If you aren't using GitHub pages, you don't need these.
|
||||
organizationName: 'daffyyyy', // Usually your GitHub org/user name.
|
||||
projectName: 'CS2-SimpleAdmin', // Usually your repo name.
|
||||
|
||||
onBrokenLinks: 'throw',
|
||||
|
||||
// Even if you don't use internationalization, you can use this field to set
|
||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
||||
// may want to replace "en" with "zh-Hans".
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en'],
|
||||
},
|
||||
|
||||
presets: [
|
||||
[
|
||||
'classic',
|
||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
({
|
||||
docs: {
|
||||
sidebarPath: './sidebars.js',
|
||||
// Please change this to your repo.
|
||||
// Remove this to remove the "edit this page" links.
|
||||
// editUrl:
|
||||
// '',
|
||||
},
|
||||
blog: false, // Disable blog
|
||||
theme: {
|
||||
customCss: './src/css/custom.css',
|
||||
},
|
||||
}),
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig:
|
||||
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
||||
({
|
||||
// Replace with your project's social card
|
||||
image: 'img/docusaurus-social-card.jpg',
|
||||
metadata: [
|
||||
{name: 'keywords', content: 'CS2, Counter-Strike 2, admin plugin, server management, bans, mutes, CounterStrikeSharp'},
|
||||
{name: 'description', content: 'Comprehensive administration plugin for Counter-Strike 2 servers with ban management, multi-server support, and extensible API'},
|
||||
{name: 'author', content: 'daffyyyy'},
|
||||
{property: 'og:title', content: 'CS2-SimpleAdmin - Admin Plugin for Counter-Strike 2'},
|
||||
{property: 'og:description', content: 'Comprehensive administration plugin for CS2 servers. Manage bans, mutes, warnings, and permissions with multi-server support.'},
|
||||
{property: 'og:type', content: 'website'},
|
||||
{property: 'og:url', content: 'https://cs2-simpleadmin.daffyy.dev'},
|
||||
{property: 'og:image', content: 'https://cs2-simpleadmin.daffyy.dev/img/docusaurus-social-card.jpg'},
|
||||
{name: 'twitter:card', content: 'summary_large_image'},
|
||||
{name: 'twitter:title', content: 'CS2-SimpleAdmin - Admin Plugin for Counter-Strike 2'},
|
||||
{name: 'twitter:description', content: 'Comprehensive administration plugin for CS2 servers with ban management and multi-server support.'},
|
||||
{name: 'twitter:image', content: 'https://cs2-simpleadmin.daffyy.dev/img/docusaurus-social-card.jpg'},
|
||||
],
|
||||
colorMode: {
|
||||
respectPrefersColorScheme: true,
|
||||
},
|
||||
navbar: {
|
||||
title: 'CS2-SimpleAdmin',
|
||||
logo: {
|
||||
alt: 'CS2-SimpleAdmin Logo',
|
||||
src: 'img/logo.svg',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'docSidebar',
|
||||
sidebarId: 'userSidebar',
|
||||
position: 'left',
|
||||
label: 'User Guide',
|
||||
},
|
||||
{
|
||||
type: 'docSidebar',
|
||||
sidebarId: 'modulesSidebar',
|
||||
position: 'left',
|
||||
label: 'Modules',
|
||||
},
|
||||
{
|
||||
type: 'docSidebar',
|
||||
sidebarId: 'developerSidebar',
|
||||
position: 'left',
|
||||
label: 'Developer',
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin',
|
||||
label: 'GitHub',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: 'dark',
|
||||
links: [
|
||||
{
|
||||
title: 'Documentation',
|
||||
items: [
|
||||
{
|
||||
label: 'User Guide',
|
||||
to: '/docs/user/intro',
|
||||
},
|
||||
{
|
||||
label: 'Modules',
|
||||
to: '/docs/modules/intro',
|
||||
},
|
||||
{
|
||||
label: 'Developer',
|
||||
to: '/docs/developer/intro',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Community',
|
||||
items: [
|
||||
{
|
||||
label: 'GitHub Issues',
|
||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin/issues',
|
||||
},
|
||||
{
|
||||
label: 'GitHub Discussions',
|
||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin/discussions',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'More',
|
||||
items: [
|
||||
{
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin',
|
||||
},
|
||||
{
|
||||
label: 'Releases',
|
||||
href: 'https://github.com/daffyyyy/CS2-SimpleAdmin/releases',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} CS2-SimpleAdmin. Built with Docusaurus.`,
|
||||
},
|
||||
prism: {
|
||||
theme: prismThemes.github,
|
||||
darkTheme: prismThemes.dracula,
|
||||
additionalLanguages: ['csharp', 'json', 'bash'],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
1575
CS2-SimpleAdmin-docs/info.txt
Normal file
17981
CS2-SimpleAdmin-docs/package-lock.json
generated
Normal file
44
CS2-SimpleAdmin-docs/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "cs-2-simple-admin-docs",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.9.2",
|
||||
"@docusaurus/preset-classic": "3.9.2",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.9.2",
|
||||
"@docusaurus/types": "3.9.2"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.5%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 3 chrome version",
|
||||
"last 3 firefox version",
|
||||
"last 5 safari version"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0"
|
||||
}
|
||||
}
|
||||
81
CS2-SimpleAdmin-docs/sidebars.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// @ts-check
|
||||
|
||||
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
||||
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
|
||||
@type {import('@docusaurus/plugin-content-docs').SidebarsConfig}
|
||||
*/
|
||||
const sidebars = {
|
||||
userSidebar: [
|
||||
'user/intro',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Getting Started',
|
||||
items: [
|
||||
'user/installation',
|
||||
'user/configuration',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Commands',
|
||||
items: [
|
||||
'user/commands/basebans',
|
||||
'user/commands/basecomms',
|
||||
'user/commands/basecommands',
|
||||
'user/commands/basechat',
|
||||
'user/commands/playercommands',
|
||||
'user/commands/basevotes',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
modulesSidebar: [
|
||||
'modules/intro',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Official Modules',
|
||||
items: [
|
||||
'modules/funcommands',
|
||||
],
|
||||
},
|
||||
'modules/development',
|
||||
],
|
||||
|
||||
developerSidebar: [
|
||||
'developer/intro',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'API Reference',
|
||||
items: [
|
||||
'developer/api/overview',
|
||||
'developer/api/commands',
|
||||
'developer/api/menus',
|
||||
'developer/api/penalties',
|
||||
'developer/api/events',
|
||||
'developer/api/utilities',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Module Development',
|
||||
items: [
|
||||
'developer/module/getting-started',
|
||||
'developer/module/best-practices',
|
||||
'developer/module/examples',
|
||||
],
|
||||
},
|
||||
'developer/architecture',
|
||||
],
|
||||
};
|
||||
|
||||
export default sidebars;
|
||||
@@ -0,0 +1,64 @@
|
||||
import clsx from 'clsx';
|
||||
import Heading from '@theme/Heading';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const FeatureList = [
|
||||
{
|
||||
title: 'Comprehensive Admin Tools',
|
||||
img: require('@site/static/img/index_1.png').default,
|
||||
description: (
|
||||
<>
|
||||
Full suite of admin commands for managing players, bans, mutes, warnings,
|
||||
and server settings. Everything you need to moderate your CS2 server.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Multi-Server Support',
|
||||
img: require('@site/static/img/index_2.png').default,
|
||||
description: (
|
||||
<>
|
||||
Manage multiple servers with synchronized admin permissions and penalties.
|
||||
Share bans, mutes, and admin groups across your entire server network.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Extensible API',
|
||||
img: require('@site/static/img/index_3.png').default,
|
||||
description: (
|
||||
<>
|
||||
Build custom modules using the public API. Create your own commands,
|
||||
menus, and integrate with CS2-SimpleAdmin's permission and penalty systems.
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
function Feature({img, title, description}) {
|
||||
return (
|
||||
<div className={clsx('col col--4')}>
|
||||
<div className="text--center">
|
||||
<img src={img} className={styles.featureSvg} alt={title} />
|
||||
</div>
|
||||
<div className="text--center padding-horiz--md">
|
||||
<Heading as="h3">{title}</Heading>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomepageFeatures() {
|
||||
return (
|
||||
<section className={styles.features}>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
{FeatureList.map((props, idx) => (
|
||||
<Feature key={idx} {...props} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.features {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.featureSvg {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
30
CS2-SimpleAdmin-docs/src/css/custom.css
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Any CSS included here will be global. The classic template
|
||||
* bundles Infima by default. Infima is a CSS framework designed to
|
||||
* work well for content-centric websites.
|
||||
*/
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-color-primary: #ff8c00;
|
||||
--ifm-color-primary-dark: #e67e00;
|
||||
--ifm-color-primary-darker: #d97700;
|
||||
--ifm-color-primary-darkest: #b36200;
|
||||
--ifm-color-primary-light: #ff9a1a;
|
||||
--ifm-color-primary-lighter: #ffa328;
|
||||
--ifm-color-primary-lightest: #ffb54d;
|
||||
--ifm-code-font-size: 95%;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||
[data-theme='dark'] {
|
||||
--ifm-color-primary: #ff9500;
|
||||
--ifm-color-primary-dark: #e68600;
|
||||
--ifm-color-primary-darker: #cc7700;
|
||||
--ifm-color-primary-darkest: #b36200;
|
||||
--ifm-color-primary-light: #ffa31a;
|
||||
--ifm-color-primary-lighter: #ffad33;
|
||||
--ifm-color-primary-lightest: #ffc266;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
52
CS2-SimpleAdmin-docs/src/pages/index.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import clsx from 'clsx';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import Layout from '@theme/Layout';
|
||||
import HomepageFeatures from '@site/src/components/HomepageFeatures';
|
||||
|
||||
import Heading from '@theme/Heading';
|
||||
import styles from './index.module.css';
|
||||
|
||||
function HomepageHeader() {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
return (
|
||||
<header className={clsx('hero hero--primary', styles.heroBanner)}>
|
||||
<iframe
|
||||
className={styles.videoBackground}
|
||||
src="https://www.youtube.com/embed/4qEdIXLdxMo?autoplay=1&mute=1&loop=1&playlist=4qEdIXLdxMo&controls=0&showinfo=0&rel=0&modestbranding=1&playsinline=1"
|
||||
title="Background Video"
|
||||
frameBorder="0"
|
||||
allow="autoplay; encrypted-media"
|
||||
allowFullScreen
|
||||
/>
|
||||
<div className={styles.videoOverlay}></div>
|
||||
<div className="container">
|
||||
<Heading as="h1" className="hero__title">
|
||||
{siteConfig.title}
|
||||
</Heading>
|
||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
||||
<div className={styles.buttons}>
|
||||
<Link
|
||||
className="button button--secondary button--lg"
|
||||
to="/docs/user/intro">
|
||||
Get Started
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
return (
|
||||
<Layout
|
||||
title={`${siteConfig.title} - Admin Plugin for CS2`}
|
||||
description="CS2-SimpleAdmin is a comprehensive administration plugin for Counter-Strike 2 servers. Manage bans, mutes, warnings, and permissions with multi-server support and extensible API.">
|
||||
<HomepageHeader />
|
||||
<main>
|
||||
<HomepageFeatures />
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
97
CS2-SimpleAdmin-docs/src/pages/index.module.css
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* CSS files with the .module.css suffix will be treated as CSS modules
|
||||
* and scoped locally.
|
||||
*/
|
||||
|
||||
.heroBanner {
|
||||
padding: 8rem 0;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 600px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.videoBackground {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100vw;
|
||||
height: 56.25vw; /* 16:9 Aspect Ratio */
|
||||
min-height: 100vh;
|
||||
min-width: 177.77vh; /* 16:9 Aspect Ratio */
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.videoOverlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.heroBanner :global(.container) {
|
||||
position: relative;
|
||||
z-index: 999 !important;
|
||||
}
|
||||
|
||||
.heroBanner :global(.hero__title) {
|
||||
color: white !important;
|
||||
text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.9);
|
||||
font-size: 3.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.heroBanner :global(.hero__subtitle) {
|
||||
color: white !important;
|
||||
text-shadow: 2px 2px 6px rgba(0, 0, 0, 0.9);
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.heroBanner .buttons {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 996px) {
|
||||
.heroBanner {
|
||||
padding: 4rem 2rem;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.videoBackground {
|
||||
width: 200vw;
|
||||
height: 112.5vw;
|
||||
}
|
||||
|
||||
.heroBanner :global(.hero__title) {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.heroBanner :global(.hero__subtitle) {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.buttons :global(.button) {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
7
CS2-SimpleAdmin-docs/src/pages/markdown-page.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Markdown page example
|
||||
---
|
||||
|
||||
# Markdown page example
|
||||
|
||||
You don't need React to write simple standalone pages.
|
||||
0
CS2-SimpleAdmin-docs/static/.nojekyll
Normal file
BIN
CS2-SimpleAdmin-docs/static/img/docusaurus-social-card.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
CS2-SimpleAdmin-docs/static/img/docusaurus.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
CS2-SimpleAdmin-docs/static/img/favicon.ico
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
CS2-SimpleAdmin-docs/static/img/index_1.png
Normal file
|
After Width: | Height: | Size: 319 KiB |
BIN
CS2-SimpleAdmin-docs/static/img/index_2.png
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
CS2-SimpleAdmin-docs/static/img/index_3.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
1
CS2-SimpleAdmin-docs/static/img/logo.svg
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
31
CS2-SimpleAdmin.sln
Normal 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
|
||||
BIN
CS2-SimpleAdmin/3rd_party/MenuManagerApi.dll
vendored
Normal file
446
CS2-SimpleAdmin/Api/CS2_SimpleAdminApi.cs
Normal file
@@ -0,0 +1,446 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Commands.Targeting;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CS2_SimpleAdmin.Managers;
|
||||
using CS2_SimpleAdmin.Menus;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace CS2_SimpleAdmin.Api;
|
||||
|
||||
public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
|
||||
{
|
||||
public event Action? OnSimpleAdminReady;
|
||||
public void OnSimpleAdminReadyEvent() => OnSimpleAdminReady?.Invoke();
|
||||
|
||||
public PlayerInfo GetPlayerInfo(CCSPlayerController player)
|
||||
{
|
||||
return !player.UserId.HasValue
|
||||
? throw new KeyNotFoundException("Player with specific UserId not found")
|
||||
: 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 TargetResult? GetTarget(CommandInfo command)
|
||||
{
|
||||
return CS2_SimpleAdmin.GetTarget(command);
|
||||
}
|
||||
|
||||
public void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false,
|
||||
params object[] messageArgs)
|
||||
{
|
||||
Helper.ShowAdminActivity(messageKey, callerName, dontPublish, messageArgs);
|
||||
}
|
||||
|
||||
public void ShowAdminActivityTranslated(string translatedMessage, string? callerName = null,
|
||||
bool dontPublish = false)
|
||||
{
|
||||
Helper.ShowAdminActivityTranslated(translatedMessage, callerName, dontPublish);
|
||||
}
|
||||
|
||||
public void ShowAdminActivityLocalized(object moduleLocalizer, string messageKey, string? callerName = null,
|
||||
bool dontPublish = false, params object[] messageArgs)
|
||||
{
|
||||
if (moduleLocalizer is not IStringLocalizer localizer)
|
||||
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
||||
|
||||
Helper.ShowAdminActivityLocalized(localizer, messageKey, callerName, dontPublish, messageArgs);
|
||||
}
|
||||
|
||||
public void RegisterMenuCategory(string categoryId, string categoryName, string permission = "@css/generic")
|
||||
{
|
||||
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryName, permission);
|
||||
}
|
||||
|
||||
public void RegisterMenuCategory(string categoryId, string categoryNameKey, string permission, object moduleLocalizer)
|
||||
{
|
||||
if (moduleLocalizer is not IStringLocalizer localizer)
|
||||
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
||||
|
||||
Menus.MenuManager.Instance.RegisterCategory(categoryId, categoryNameKey, permission, localizer);
|
||||
}
|
||||
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuName,
|
||||
Func<CCSPlayerController, object> menuFactory, string? permission = null, string? commandName = null)
|
||||
{
|
||||
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuName, BuilderFactory, permission, commandName);
|
||||
return;
|
||||
|
||||
MenuBuilder BuilderFactory(CCSPlayerController player)
|
||||
{
|
||||
if (menuFactory(player) is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu factory must return MenuBuilder");
|
||||
|
||||
// Dodaj automatyczną obsługę przycisku 'Wróć'
|
||||
menuBuilder.WithBackAction(p =>
|
||||
{
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
|
||||
{
|
||||
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
Menus.MenuManager.Instance.OpenMainMenu(p);
|
||||
}
|
||||
});
|
||||
|
||||
return menuBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuName,
|
||||
Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission = null, string? commandName = null)
|
||||
{
|
||||
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuName, BuilderFactory, permission, commandName);
|
||||
return;
|
||||
|
||||
MenuBuilder BuilderFactory(CCSPlayerController player)
|
||||
{
|
||||
var context = new MenuContext(categoryId, menuId, menuName, permission, commandName);
|
||||
|
||||
if (menuFactory(player, context) is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu factory must return MenuBuilder");
|
||||
|
||||
// Dodaj automatyczną obsługę przycisku 'Wróć'
|
||||
menuBuilder.WithBackAction(p =>
|
||||
{
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
|
||||
{
|
||||
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
Menus.MenuManager.Instance.OpenMainMenu(p);
|
||||
}
|
||||
});
|
||||
|
||||
return menuBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterMenu(string categoryId, string menuId, string menuNameKey,
|
||||
Func<CCSPlayerController, MenuContext, object> menuFactory, string? permission, string? commandName, object moduleLocalizer)
|
||||
{
|
||||
if (moduleLocalizer is not IStringLocalizer localizer)
|
||||
throw new InvalidOperationException("moduleLocalizer must be an IStringLocalizer instance");
|
||||
|
||||
Menus.MenuManager.Instance.RegisterMenu(categoryId, menuId, menuNameKey, BuilderFactory, permission, commandName, localizer);
|
||||
return;
|
||||
|
||||
MenuBuilder BuilderFactory(CCSPlayerController player)
|
||||
{
|
||||
var context = new MenuContext(categoryId, menuId, menuNameKey, permission, commandName);
|
||||
|
||||
if (menuFactory(player, context) is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu factory must return MenuBuilder");
|
||||
|
||||
// Dodaj automatyczną obsługę przycisku 'Wróć'
|
||||
menuBuilder.WithBackAction(p =>
|
||||
{
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
|
||||
{
|
||||
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
Menus.MenuManager.Instance.OpenMainMenu(p);
|
||||
}
|
||||
});
|
||||
|
||||
return menuBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void UnregisterMenu(string categoryId, string menuId)
|
||||
{
|
||||
Menus.MenuManager.Instance.UnregisterMenu(categoryId, menuId);
|
||||
}
|
||||
|
||||
public object CreateMenuWithBack(string title, string categoryId, CCSPlayerController player)
|
||||
{
|
||||
var builder = new MenuBuilder(title);
|
||||
builder.WithBackAction(p =>
|
||||
{
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(categoryId, out var category))
|
||||
{
|
||||
Menus.MenuManager.Instance.CreateCategoryMenuPublic(category, p).OpenMenu(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
Menus.MenuManager.Instance.OpenMainMenu(p);
|
||||
}
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public object CreateMenuWithBack(MenuContext context, CCSPlayerController player)
|
||||
{
|
||||
// Get translated title if module has localizer
|
||||
string title = context.MenuTitle;
|
||||
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(context.CategoryId, out var category))
|
||||
{
|
||||
// Check if this specific menu has a localizer
|
||||
if (category.MenuLocalizers.TryGetValue(context.MenuId, out var menuLocalizer))
|
||||
{
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
title = menuLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||
}
|
||||
}
|
||||
// Fallback to category localizer
|
||||
else if (category.ModuleLocalizer != null)
|
||||
{
|
||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||
{
|
||||
title = category.ModuleLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CreateMenuWithBack(title, context.CategoryId, player);
|
||||
}
|
||||
|
||||
public List<CCSPlayerController> GetValidPlayers()
|
||||
{
|
||||
return Helper.GetValidPlayers();
|
||||
}
|
||||
|
||||
public object CreateMenuWithPlayers(string title, string categoryId, CCSPlayerController admin,
|
||||
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
|
||||
{
|
||||
var menu = (MenuBuilder)CreateMenuWithBack(title, categoryId, admin);
|
||||
var players = Helper.GetValidPlayers().Where(filter);
|
||||
|
||||
foreach (var player in players)
|
||||
{
|
||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
||||
menu.AddOption(playerName, _ =>
|
||||
{
|
||||
if (player.IsValid)
|
||||
{
|
||||
onSelect(admin, player);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
public object CreateMenuWithPlayers(MenuContext context, CCSPlayerController admin,
|
||||
Func<CCSPlayerController, bool> filter, Action<CCSPlayerController, CCSPlayerController> onSelect)
|
||||
{
|
||||
// Get translated title if module has localizer
|
||||
string title = context.MenuTitle;
|
||||
|
||||
if (Menus.MenuManager.Instance.GetMenuCategories().TryGetValue(context.CategoryId, out var category))
|
||||
{
|
||||
// Check if this specific menu has a localizer
|
||||
if (category.MenuLocalizers.TryGetValue(context.MenuId, out var menuLocalizer))
|
||||
{
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
title = menuLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||
}
|
||||
}
|
||||
// Fallback to category localizer
|
||||
else if (category.ModuleLocalizer != null)
|
||||
{
|
||||
using (new WithTemporaryCulture(admin.GetLanguage()))
|
||||
{
|
||||
title = category.ModuleLocalizer[context.MenuTitle] ?? context.MenuTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CreateMenuWithPlayers(title, context.CategoryId, admin, filter, onSelect);
|
||||
}
|
||||
|
||||
public void AddMenuOption(object menu, string name, Action<CCSPlayerController> action, bool disabled = false,
|
||||
string? permission = null)
|
||||
{
|
||||
if (menu is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
|
||||
|
||||
menuBuilder.AddOption(name, action, disabled, permission);
|
||||
}
|
||||
|
||||
public void AddSubMenu(object menu, string name, Func<CCSPlayerController, object> subMenuFactory,
|
||||
bool disabled = false, string? permission = null)
|
||||
{
|
||||
if (menu is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
|
||||
|
||||
menuBuilder.AddSubMenu(name, player =>
|
||||
{
|
||||
var subMenu = subMenuFactory(player);
|
||||
if (subMenu is not MenuBuilder builder)
|
||||
throw new InvalidOperationException("SubMenu factory must return MenuBuilder");
|
||||
return builder;
|
||||
}, disabled, permission);
|
||||
}
|
||||
|
||||
public void OpenMenu(object menu, CCSPlayerController player)
|
||||
{
|
||||
if (menu is not MenuBuilder menuBuilder)
|
||||
throw new InvalidOperationException("Menu must be a MenuBuilder instance");
|
||||
|
||||
menuBuilder.OpenMenu(player);
|
||||
}
|
||||
}
|
||||
271
CS2-SimpleAdmin/CS2-SimpleAdmin.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
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_SimpleAdmin.Menus;
|
||||
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";
|
||||
public override string ModuleVersion => "1.7.8-beta-9";
|
||||
|
||||
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.Connected == PlayerConnectedState.PlayerConnected && !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();
|
||||
|
||||
Menus.MenuManager.Instance.InitializeDefaultCategories();
|
||||
BasicMenu.Initialize();
|
||||
}
|
||||
|
||||
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();
|
||||
AddTimer(0.5f, 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);
|
||||
}
|
||||
|
||||
internal 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}");
|
||||
}
|
||||
}
|
||||
152
CS2-SimpleAdmin/CS2-SimpleAdmin.csproj
Normal file
@@ -0,0 +1,152 @@
|
||||
<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.361">
|
||||
<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.5.0" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||
<PackageReference Include="System.Linq.Async" Version="7.0.0" />
|
||||
<PackageReference Include="ZLinq" Version="1.5.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CS2-SimpleAdminApi\CS2-SimpleAdminApi.csproj" />
|
||||
</ItemGroup>
|
||||
<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\Mysql\016_OptimizeTablesAndIndexes.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>
|
||||
<None Update="Database\Migrations\Sqlite\016_OptimizeTablesAndIndexes.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>
|
||||
237
CS2-SimpleAdmin/Commands/RegisterCommands.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
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_slay", CS2_SimpleAdmin.Instance.OnSlayCommand),
|
||||
new("css_slap", CS2_SimpleAdmin.Instance.OnSlapCommand),
|
||||
new("css_team", CS2_SimpleAdmin.Instance.OnTeamCommand),
|
||||
new("css_rename", CS2_SimpleAdmin.Instance.OnRenameCommand),
|
||||
new("css_prename", CS2_SimpleAdmin.Instance.OnPrenameCommand),
|
||||
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_slay", new Command { Aliases = ["css_slay"] } },
|
||||
{ "css_slap", new Command { Aliases = ["css_slap"] } },
|
||||
{ "css_team", new Command { Aliases = ["css_team"] } },
|
||||
{ "css_rename", new Command { Aliases = ["css_rename"] } },
|
||||
{ "css_prename", new Command { Aliases = ["css_prename"] } },
|
||||
{ "css_resize", new Command { Aliases = ["css_resize", "css_size"] } },
|
||||
{ "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)
|
||||
{
|
||||
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 _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;
|
||||
}
|
||||
}
|
||||
580
CS2-SimpleAdmin/Commands/basebans.cs
Normal 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)
|
||||
{
|
||||
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}.");
|
||||
}
|
||||
}
|
||||
150
CS2-SimpleAdmin/Commands/basechat.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
1279
CS2-SimpleAdmin/Commands/basecommands.cs
Normal file
973
CS2-SimpleAdmin/Commands/basecomms.cs
Normal 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
98
CS2-SimpleAdmin/Commands/basevotes.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
307
CS2-SimpleAdmin/Commands/funcommands.cs
Normal 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)}");
|
||||
// }
|
||||
// }
|
||||
625
CS2-SimpleAdmin/Commands/playercommands.cs
Normal file
@@ -0,0 +1,625 @@
|
||||
using System.Globalization;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
namespace CS2_SimpleAdmin;
|
||||
|
||||
public partial class CS2_SimpleAdmin
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes the 'slay' command, forcing the targeted players to commit suicide.
|
||||
/// Checks player validity and permissions.
|
||||
/// </summary>
|
||||
/// <param name="caller">Player or console issuing the command.</param>
|
||||
/// <param name="command">Command details, including targets.</param>
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnSlayCommand(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 =>
|
||||
{
|
||||
Slay(caller, player, callerName, command);
|
||||
});
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the actual slay action on a player, with notification and logging.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin or console issuing the slay.</param>
|
||||
/// <param name="player">Target player to slay.</param>
|
||||
/// <param name="callerName">Optional name to display as the slayer.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
internal static void Slay(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||
{
|
||||
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) return;
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Make the player commit suicide
|
||||
player.CommitSuicide(false, true);
|
||||
player.EmitSound("Player.Death");
|
||||
|
||||
// Determine message keys and arguments for the slay notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_slay_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_slay {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies damage as a slap effect to the targeted players.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin executing the slap command.</param>
|
||||
/// <param name="command">The command including targets and optional damage value.</param>
|
||||
[RequiresPermissions("@css/slay")]
|
||||
[CommandHelper(minArgs: 1, usage: "<#userid or name> [damage]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnSlapCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var damage = 0;
|
||||
|
||||
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();
|
||||
|
||||
if (command.ArgCount >= 2)
|
||||
{
|
||||
int.TryParse(command.GetArg(2), out damage);
|
||||
}
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
if (player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
return;
|
||||
|
||||
if (caller!.CanTarget(player))
|
||||
{
|
||||
Slap(caller, player, damage, command);
|
||||
}
|
||||
});
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies slap damage to a specific player with notifications and logging.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin applying the slap effect.</param>
|
||||
/// <param name="player">The target player to slap.</param>
|
||||
/// <param name="damage">The damage amount to apply.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
internal static void Slap(CCSPlayerController? caller, CCSPlayerController player, int damage, 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";
|
||||
|
||||
// Apply slap damage to the player
|
||||
player.Pawn.Value?.Slap(damage);
|
||||
player.EmitSound("Player.DamageFall");
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_slap {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {damage}");
|
||||
|
||||
// Determine message key and arguments for the slap notification
|
||||
var (activityMessageKey, adminActivityArgs) =
|
||||
("sa_admin_slap_message",
|
||||
new object[] { "CALLER", player.PlayerName });
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
|
||||
|
||||
if (_localizer != null)
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the team of targeted players with optional kill on switch.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin issuing the command.</param>
|
||||
/// <param name="command">The command containing targets, team info, and optional kill flag.</param>
|
||||
[RequiresPermissions("@css/kick")]
|
||||
[CommandHelper(minArgs: 2, usage: "<#userid or name> [<ct/tt/spec>] [-k]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnTeamCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
var teamName = command.GetArg(2).ToLower();
|
||||
string _teamName;
|
||||
var teamNum = CsTeam.Spectator;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
||||
|
||||
switch (teamName)
|
||||
{
|
||||
case "ct":
|
||||
case "counterterrorist":
|
||||
teamNum = CsTeam.CounterTerrorist;
|
||||
_teamName = "CT";
|
||||
break;
|
||||
|
||||
case "t":
|
||||
case "tt":
|
||||
case "terrorist":
|
||||
teamNum = CsTeam.Terrorist;
|
||||
_teamName = "TT";
|
||||
break;
|
||||
|
||||
case "swap":
|
||||
_teamName = "SWAP";
|
||||
break;
|
||||
|
||||
default:
|
||||
teamNum = CsTeam.Spectator;
|
||||
_teamName = "SPEC";
|
||||
break;
|
||||
}
|
||||
|
||||
var kill = command.GetArg(3).ToLower().Equals("-k");
|
||||
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
ChangeTeam(caller, player, _teamName, teamNum, kill, command);
|
||||
});
|
||||
|
||||
Helper.LogCommand(caller, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the team of a player with various conditions and logs the operation.
|
||||
/// </summary>
|
||||
/// <param name="caller">The player/admin issuing the change.</param>
|
||||
/// <param name="player">The target player.</param>
|
||||
/// <param name="teamName">Team name string.</param>
|
||||
/// <param name="teamNum">Team enumeration value.</param>
|
||||
/// <param name="kill">If true, kills player on team change.</param>
|
||||
/// <param name="command">Optional command info for logging.</param>
|
||||
internal static void ChangeTeam(CCSPlayerController? caller, CCSPlayerController player, string teamName, CsTeam teamNum, bool kill, CommandInfo? command = null)
|
||||
{
|
||||
// Check if the player is valid and connected
|
||||
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected)
|
||||
return;
|
||||
|
||||
// Ensure the caller can target the player
|
||||
if (!caller.CanTarget(player)) return;
|
||||
|
||||
// Set default caller name if not provided
|
||||
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
|
||||
|
||||
// Change team based on the provided teamName and conditions
|
||||
if (!teamName.Equals("swap", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && teamNum != CsTeam.Spectator && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
|
||||
player.SwitchTeam(teamNum);
|
||||
else
|
||||
player.ChangeTeam(teamNum);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (player.TeamNum != (byte)CsTeam.Spectator)
|
||||
{
|
||||
var _teamNum = (CsTeam)player.TeamNum == CsTeam.Terrorist ? CsTeam.CounterTerrorist : CsTeam.Terrorist;
|
||||
teamName = _teamNum == CsTeam.Terrorist ? "TT" : "CT";
|
||||
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
|
||||
player.SwitchTeam(_teamNum);
|
||||
else
|
||||
player.ChangeTeam(_teamNum);
|
||||
}
|
||||
}
|
||||
|
||||
// Log the command
|
||||
if (command == null)
|
||||
Helper.LogCommand(caller, $"css_team {player.PlayerName} {teamName}");
|
||||
|
||||
// Determine message key and arguments for the team change notification
|
||||
var activityMessageKey = "sa_admin_team_message";
|
||||
var adminActivityArgs = new object[] { "CALLER", player.PlayerName, teamName };
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
|
||||
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renames targeted players to a new name.
|
||||
/// </summary>
|
||||
/// <param name="caller">The admin issuing the rename command.</param>
|
||||
/// <param name="command">The command including targets and new name.</param>
|
||||
[CommandHelper(1, "<#userid or name> <new name>")]
|
||||
[RequiresPermissions("@css/kick")]
|
||||
public void OnRenameCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Set default caller name if not provided
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
// Get the new name from the command arguments
|
||||
var newName = command.GetArg(2);
|
||||
|
||||
// Check if the new name is valid
|
||||
if (string.IsNullOrEmpty(newName))
|
||||
return;
|
||||
|
||||
// Retrieve the targets based on the command
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
// Filter out valid players from the targets
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
||||
|
||||
// Log the command
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Process each player to rename
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
// Check if the player is connected and can be targeted
|
||||
if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player))
|
||||
return;
|
||||
|
||||
// Determine message key and arguments for the rename notification
|
||||
var activityMessageKey = "sa_admin_rename_message";
|
||||
var adminActivityArgs = new object[] { "CALLER", player.PlayerName, newName };
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller != null && SilentPlayers.Contains(caller.Slot)) return;
|
||||
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
|
||||
// Rename the player
|
||||
player.Rename(newName);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renames permamently targeted players to a new name.
|
||||
/// </summary>
|
||||
/// <param name="caller">The admin issuing the pre-rename command.</param>
|
||||
/// <param name="command">The command containing targets and new alias.</param>
|
||||
[CommandHelper(1, "<#userid or name> <new name>")]
|
||||
[RequiresPermissions("@css/ban")]
|
||||
public void OnPrenameCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
// Set default caller name if not provided
|
||||
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
|
||||
|
||||
// Get the new name from the command arguments
|
||||
var newName = command.GetArg(2);
|
||||
|
||||
// Retrieve the targets based on the command
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null) return;
|
||||
|
||||
// Filter out valid players from the targets
|
||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
|
||||
|
||||
// Log the command
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
// Process each player to rename
|
||||
playersToTarget.ForEach(player =>
|
||||
{
|
||||
// Check if the player is connected and can be targeted
|
||||
if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player))
|
||||
return;
|
||||
|
||||
// Determine message key and arguments for the rename notification
|
||||
var activityMessageKey = "sa_admin_rename_message";
|
||||
var adminActivityArgs = new object[] { "CALLER", player.PlayerName, newName };
|
||||
|
||||
// Display admin activity message to other players
|
||||
if (caller != null && !SilentPlayers.Contains(caller.Slot))
|
||||
{
|
||||
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
|
||||
}
|
||||
|
||||
// Determine if the new name is valid and update the renamed players list
|
||||
if (!string.IsNullOrEmpty(newName))
|
||||
{
|
||||
RenamedPlayers[player.SteamID] = newName;
|
||||
player.Rename(newName);
|
||||
}
|
||||
else
|
||||
{
|
||||
RenamedPlayers.Remove(player.SteamID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports targeted player(s) to another player's location.
|
||||
/// </summary>
|
||||
/// <param name="caller">Admin issuing teleport command.</param>
|
||||
/// <param name="command">Command containing teleport targets and destination.</param>
|
||||
[CommandHelper(1, "<#userid or name> [#userid or name]")]
|
||||
[RequiresPermissions("@css/kick")]
|
||||
public void OnGotoCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
IEnumerable<CCSPlayerController> playersToTeleport;
|
||||
CCSPlayerController? destinationPlayer;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
|
||||
if (command.ArgCount < 3)
|
||||
{
|
||||
if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
|
||||
return;
|
||||
|
||||
if (targets == null || targets.Count() != 1)
|
||||
return;
|
||||
|
||||
destinationPlayer = targets.Players.FirstOrDefault(p =>
|
||||
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
|
||||
|
||||
if (destinationPlayer == null || !caller.CanTarget(destinationPlayer) || caller.PlayerPawn.Value == null)
|
||||
return;
|
||||
|
||||
playersToTeleport = [caller];
|
||||
}
|
||||
else
|
||||
{
|
||||
var destination = GetTarget(command, 2);
|
||||
if (targets == null || destination == null || destination.Count() != 1)
|
||||
return;
|
||||
|
||||
destinationPlayer = destination.Players.FirstOrDefault(p =>
|
||||
p is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
|
||||
|
||||
if (destinationPlayer == null)
|
||||
return;
|
||||
|
||||
playersToTeleport = targets.Players
|
||||
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
|
||||
.ToList();
|
||||
|
||||
if (!playersToTeleport.Any())
|
||||
return;
|
||||
}
|
||||
|
||||
// Log command
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
foreach (var player in playersToTeleport)
|
||||
{
|
||||
if (player.PlayerPawn?.Value == null || destinationPlayer?.PlayerPawn?.Value == null)
|
||||
continue;
|
||||
|
||||
player.TeleportPlayer(destinationPlayer);
|
||||
|
||||
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
|
||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
|
||||
AddTimer(4, () =>
|
||||
{
|
||||
if (player is { IsValid: true, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
|
||||
{
|
||||
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
}
|
||||
|
||||
if (destinationPlayer.IsValid && destinationPlayer.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
|
||||
{
|
||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
}
|
||||
});
|
||||
|
||||
if (caller != null && !SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
||||
{
|
||||
Helper.ShowAdminActivity("sa_admin_tp_message", player.PlayerName, false, "CALLER", destinationPlayer.PlayerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Brings targeted player(s) to the caller or specified destination player's location.
|
||||
/// </summary>
|
||||
/// <param name="caller">Player issuing the bring command.</param>
|
||||
/// <param name="command">Command containing the destination and targets.</param>
|
||||
[CommandHelper(1, "<#destination or name> [#userid or name...]")]
|
||||
[RequiresPermissions("@css/kick")]
|
||||
public void OnBringCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
{
|
||||
IEnumerable<CCSPlayerController> playersToTeleport;
|
||||
CCSPlayerController? destinationPlayer;
|
||||
|
||||
if (command.ArgCount < 3)
|
||||
{
|
||||
if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
|
||||
return;
|
||||
|
||||
var targets = GetTarget(command);
|
||||
if (targets == null || !targets.Any())
|
||||
return;
|
||||
|
||||
destinationPlayer = caller;
|
||||
|
||||
playersToTeleport = targets.Players
|
||||
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
var destination = GetTarget(command);
|
||||
if (destination == null || destination.Count() != 1)
|
||||
return;
|
||||
|
||||
destinationPlayer = destination.Players.FirstOrDefault(p =>
|
||||
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
|
||||
|
||||
if (destinationPlayer == null)
|
||||
return;
|
||||
|
||||
// Rest args = targets to teleport
|
||||
var targets = GetTarget(command, 2);
|
||||
if (targets == null || !targets.Any())
|
||||
return;
|
||||
|
||||
playersToTeleport = targets.Players
|
||||
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller!.CanTarget(p))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (destinationPlayer == null || !playersToTeleport.Any())
|
||||
return;
|
||||
|
||||
// Log command
|
||||
Helper.LogCommand(caller, command);
|
||||
|
||||
foreach (var player in playersToTeleport)
|
||||
{
|
||||
if (player.PlayerPawn?.Value == null || destinationPlayer.PlayerPawn?.Value == null)
|
||||
continue;
|
||||
|
||||
// Teleport
|
||||
player.TeleportPlayer(destinationPlayer);
|
||||
|
||||
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
|
||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
|
||||
AddTimer(4, () =>
|
||||
{
|
||||
if (player is { IsValid: true, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
|
||||
{
|
||||
player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
}
|
||||
|
||||
if (destinationPlayer.IsValid && destinationPlayer.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
|
||||
{
|
||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
|
||||
Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
}
|
||||
});
|
||||
|
||||
if (caller != null && !SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
||||
{
|
||||
Helper.ShowAdminActivity("sa_admin_bring_message", player.PlayerName, false, "CALLER", destinationPlayer.PlayerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// [CommandHelper(1, "<#userid or name> [#userid or name]")]
|
||||
// [RequiresPermissions("@css/kick")]
|
||||
// public void OnBringCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
// {
|
||||
// // Check if the caller is valid and has a live pawn
|
||||
// if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
|
||||
// return;
|
||||
//
|
||||
// // Get the target players
|
||||
// var targets = GetTarget(command);
|
||||
// if (targets == null || targets.Count() > 1) return;
|
||||
//
|
||||
// var playersToTarget = targets.Players
|
||||
// .Where(player => player is { IsValid: true, IsHLTV: false })
|
||||
// .ToList();
|
||||
//
|
||||
// // Log the command
|
||||
// Helper.LogCommand(caller, command);
|
||||
//
|
||||
// // Process each player to teleport
|
||||
// foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).Where(caller.CanTarget))
|
||||
// {
|
||||
// if (caller.PlayerPawn.Value == null || player.PlayerPawn.Value == null)
|
||||
// continue;
|
||||
//
|
||||
// // Teleport the player to the caller and toggle noclip
|
||||
// player.TeleportPlayer(caller);
|
||||
// // caller.PlayerPawn.Value.ToggleNoclip();
|
||||
//
|
||||
// caller.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
// caller.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
//
|
||||
// Utilities.SetStateChanged(caller, "CCollisionProperty", "m_CollisionGroup");
|
||||
// Utilities.SetStateChanged(caller, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
//
|
||||
// player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
// player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
|
||||
//
|
||||
// Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
||||
// Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
//
|
||||
// // Set a timer to toggle collision back after 4 seconds
|
||||
// AddTimer(4, () =>
|
||||
// {
|
||||
// if (!player.IsValid || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
|
||||
// return;
|
||||
//
|
||||
// caller.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
// caller.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
//
|
||||
// Utilities.SetStateChanged(caller, "CCollisionProperty", "m_CollisionGroup");
|
||||
// Utilities.SetStateChanged(caller, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
//
|
||||
// player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
// player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
|
||||
//
|
||||
// Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
|
||||
// Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
|
||||
// });
|
||||
//
|
||||
// // Prepare message key and arguments for the bring notification
|
||||
// var activityMessageKey = "sa_admin_bring_message";
|
||||
// var adminActivityArgs = new object[] { "CALLER", player.PlayerName };
|
||||
//
|
||||
// // Show admin activity
|
||||
// if (!SilentPlayers.Contains(caller.Slot) && _localizer != null)
|
||||
// {
|
||||
// Helper.ShowAdminActivity(activityMessageKey, caller.PlayerName, false, adminActivityArgs);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
325
CS2-SimpleAdmin/Config.cs
Normal 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
|
||||
}
|
||||
69
CS2-SimpleAdmin/Database/Database.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
CS2-SimpleAdmin/Database/IDatabaseProvider.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
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);
|
||||
|
||||
// PlayerManager
|
||||
string GetUpsertPlayerIpQuery();
|
||||
|
||||
// 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);
|
||||
}
|
||||
105
CS2-SimpleAdmin/Database/Migration.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
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())
|
||||
{
|
||||
if (migrationsPath.Contains("sqlite", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
cmd.CommandText = """
|
||||
CREATE TABLE IF NOT EXISTS sa_migrations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
version TEXT NOT NULL
|
||||
);
|
||||
|
||||
""";
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.CommandText = """
|
||||
CREATE TABLE IF NOT EXISTS sa_migrations (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
version VARCHAR(128) 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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`;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,25 @@
|
||||
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;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `sa_groups_servers` CHANGE `server_id` `server_id` INT(11) NULL;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `sa_mutes` ADD `passed` INT NULL AFTER `duration`;
|
||||
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
|
||||
`steamid` bigint(20) NOT NULL,
|
||||
`address` varchar(64) NOT NULL,
|
||||
`used_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`steamid`, `address`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `sa_servers` ADD `rcon_password` varchar(128) NULL AFTER `hostname`;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `sa_bans` ADD COLUMN `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `status`;
|
||||
@@ -0,0 +1,5 @@
|
||||
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`;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
-- -- Migration 016: Optimize tables and indexes
|
||||
-- -- Add proper indexes for all tables to improve query performance
|
||||
|
||||
-- -- Optimize sa_players_ips table indexes
|
||||
-- -- Add index on used_at for efficient date-based queries
|
||||
-- ALTER TABLE `sa_players_ips` ADD INDEX IF NOT EXISTS `idx_used_at` (`used_at` DESC);
|
||||
|
||||
-- -- Optimize sa_bans table indexes
|
||||
-- -- Add composite indexes for common query patterns
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
|
||||
|
||||
-- -- Optimize sa_admins table indexes
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
|
||||
|
||||
-- -- Optimize sa_mutes table indexes (in addition to migration 014)
|
||||
-- -- Add index for expire queries
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
|
||||
|
||||
-- -- Optimize sa_warns table indexes (if exists)
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
|
||||
|
||||
-- -- Add index on sa_servers for faster lookups
|
||||
-- CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
|
||||
@@ -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`)
|
||||
);
|
||||
@@ -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
|
||||
);
|
||||
@@ -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) != '';
|
||||
@@ -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;
|
||||
@@ -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;
|
||||