mirror of
https://github.com/roflmuffin/CounterStrikeSharp.git
synced 2025-12-06 08:03:12 -08:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fdbb9500b | ||
|
|
0ab3cf429a | ||
|
|
1f9630b92d | ||
|
|
02bf2483d3 | ||
|
|
cb181b6a49 | ||
|
|
cc21dca5a0 | ||
|
|
5721d060ea | ||
|
|
220521d571 | ||
|
|
5698b511e9 | ||
|
|
48c9d195ff | ||
|
|
603827d331 | ||
|
|
e557d54c32 | ||
|
|
48d3ade5cf | ||
|
|
77b05e912e | ||
|
|
1354b4972d | ||
|
|
8b5eb7e38d | ||
|
|
2dd62c44d3 | ||
|
|
f811338ce4 | ||
|
|
194c340ae7 | ||
|
|
6b0912d3cd | ||
|
|
2d3aa09aa4 | ||
|
|
911084e71e | ||
|
|
5b99206568 | ||
|
|
4bfdf28beb | ||
|
|
9bcd0f7e92 | ||
|
|
11c6486ec5 | ||
|
|
ee69560a66 | ||
|
|
d37e5e194a | ||
|
|
c4740d1cc9 | ||
|
|
7e92f178fd | ||
|
|
107ca08132 | ||
|
|
8cda8d9a50 | ||
|
|
3d59a05de8 | ||
|
|
77b7040d6c | ||
|
|
75de9732ef | ||
|
|
575c859ddb | ||
|
|
e12a7cb17a | ||
|
|
7c7f52a219 | ||
|
|
cd593fb238 | ||
|
|
c5cc65be48 | ||
|
|
59928bbcc5 | ||
|
|
319b116c5f | ||
|
|
e0dc053d22 | ||
|
|
f2e0dac32d | ||
|
|
4e8c18abc7 | ||
|
|
8d1891a3a8 | ||
|
|
6bc43444f7 | ||
|
|
f0c7869f4a | ||
|
|
3e38ed3c77 | ||
|
|
7e9e7c6665 | ||
|
|
9a018f295b | ||
|
|
8b725d435f | ||
|
|
ea3596417a | ||
|
|
123f41914e | ||
|
|
8f69076405 | ||
|
|
44a85d1201 | ||
|
|
20f50289ee | ||
|
|
bb5fb5de72 | ||
|
|
6147739cfa | ||
|
|
3ab5893f22 | ||
|
|
50ce09a7b3 | ||
|
|
9c7944e6f1 | ||
|
|
bc71aa7739 | ||
|
|
16a1efc0cb | ||
|
|
8ae85cedf4 | ||
|
|
8e2234ff25 | ||
|
|
04e7ed682a | ||
|
|
15e1260146 | ||
|
|
517607d962 | ||
|
|
0f72631eb0 | ||
|
|
75fcf21fb7 | ||
|
|
0ddf6bcdfa | ||
|
|
98661cd069 | ||
|
|
86a5699b40 | ||
|
|
414710d05c | ||
|
|
b09c2b62c8 | ||
|
|
31760518ed | ||
|
|
6a160bcc3d | ||
|
|
9c8e9db56e | ||
|
|
e2e0eab87d | ||
|
|
43292bb1d2 | ||
|
|
12c54cd4fc | ||
|
|
e155a70873 | ||
|
|
69d9b5d2c8 | ||
|
|
933fdf9d81 | ||
|
|
18e9e37a98 | ||
|
|
fe236806e1 | ||
|
|
2c4e9bca42 | ||
|
|
8f3e0c226b | ||
|
|
5f6ccf2839 | ||
|
|
6c2f56793b | ||
|
|
cc7dd5ca96 | ||
|
|
ebc361b2f8 | ||
|
|
c72eff2546 | ||
|
|
4b432e9efc | ||
|
|
22bbf835c7 | ||
|
|
092a6077c3 | ||
|
|
4430060efd | ||
|
|
77ea6fd80d | ||
|
|
f18df3df2b |
203
.github/workflows/cmake-single-platform.yml
vendored
203
.github/workflows/cmake-single-platform.yml
vendored
@@ -5,86 +5,193 @@ on:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
branches: [ "main" ]
|
||||
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: registry.gitlab.steamos.cloud/steamrt/sniper/sdk:latest
|
||||
|
||||
build_windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Prepare env
|
||||
shell: bash
|
||||
run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup protobuf
|
||||
shell: bash
|
||||
run: sudo apt-get update && sudo apt install -y protobuf-compiler
|
||||
- name: Visual Studio environment
|
||||
shell: cmd
|
||||
run: |
|
||||
:: See https://github.com/microsoft/vswhere/wiki/Find-VC
|
||||
for /f "usebackq delims=*" %%i in (`vswhere -latest -property installationPath`) do (
|
||||
call "%%i"\Common7\Tools\vsdevcmd.bat -arch=x64 -host_arch=x64
|
||||
)
|
||||
|
||||
:: Loop over all environment variables and make them global.
|
||||
for /f "delims== tokens=1,2" %%a in ('set') do (
|
||||
echo>>"%GITHUB_ENV%" %%a=%%b
|
||||
)
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Generate build number
|
||||
id: buildnumber
|
||||
uses: onyxmueller/build-tag-number@v1
|
||||
with:
|
||||
token: ${{secrets.github_token}}
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '7.0.x'
|
||||
- run: dotnet publish -c Release /p:Version=1.0.${{ env.BUILD_NUMBER }} managed/CounterStrikeSharp.API
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build build --config ${{env.BUILD_TYPE}}
|
||||
run: |
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ..
|
||||
cmake --build . --config ${{env.BUILD_TYPE}} -- /m:16
|
||||
|
||||
- name: Clean build directory
|
||||
run: |
|
||||
mkdir -p build/addons/counterstrikesharp/bin/win64
|
||||
mv build/${{env.BUILD_TYPE}}/*.dll build/addons/counterstrikesharp/bin/win64
|
||||
mkdir build/output/
|
||||
mv build/addons build/output
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: counterstrikesharp-build-windows-${{ env.GITHUB_SHA_SHORT }}
|
||||
path: build/output/
|
||||
|
||||
build_linux:
|
||||
runs-on: ubuntu-latest
|
||||
# Could not figure out how to run in a container only on some matrix paths, so I've split it out into its own build.
|
||||
container:
|
||||
image: registry.gitlab.steamos.cloud/steamrt/sniper/sdk:latest
|
||||
steps:
|
||||
- name: Prepare env
|
||||
shell: bash
|
||||
run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ..
|
||||
cmake --build . --config ${{env.BUILD_TYPE}} -- -j16
|
||||
|
||||
- name: Clean build directory
|
||||
run: |
|
||||
mkdir build/output/
|
||||
mv build/addons build/output
|
||||
|
||||
- name: Add API to Artifacts
|
||||
run: |
|
||||
mkdir -p build/output/addons/counterstrikesharp/api
|
||||
cp -r managed/CounterStrikeSharp.API/bin/Release/net7.0/publish/* build/output/addons/counterstrikesharp/api
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: counterstrikesharp-build-linux-${{ env.GITHUB_SHA_SHORT }}
|
||||
path: build/output/
|
||||
|
||||
build_managed:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
buildnumber: ${{ steps.buildnumber.outputs.build_number }}
|
||||
steps:
|
||||
- name: Prepare env
|
||||
shell: bash
|
||||
run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
- name: Fallback build number
|
||||
if: github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
run: echo "BUILD_NUMBER=0" >> $GITHUB_ENV
|
||||
|
||||
# We don't need expensive submodules for the managed side.
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Generate build number
|
||||
if: github.event_name == 'push'
|
||||
id: buildnumber
|
||||
uses: onyxmueller/build-tag-number@v1
|
||||
with:
|
||||
token: ${{secrets.github_token}}
|
||||
|
||||
- name: Build runtime v${{ env.BUILD_NUMBER }}
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '7.0.x'
|
||||
|
||||
- run: |
|
||||
dotnet publish -c Release /p:Version=1.0.${{ env.BUILD_NUMBER }} managed/CounterStrikeSharp.API
|
||||
dotnet pack -c Release /p:Version=1.0.${{ env.BUILD_NUMBER }} managed/CounterStrikeSharp.API
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: counterstrikesharp-build-${{ env.BUILD_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}
|
||||
path: build/output/
|
||||
name: counterstrikesharp-build-api-${{ env.GITHUB_SHA_SHORT }}
|
||||
path: managed/CounterStrikeSharp.API/bin/Release
|
||||
|
||||
- name: Zip CounterStrikeSharp Build
|
||||
run: (cd build/output && zip -qq -r ../../counterstrikesharp-build-${{ env.BUILD_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}.zip *)
|
||||
publish:
|
||||
if: github.event_name == 'push'
|
||||
permissions:
|
||||
contents: write
|
||||
needs: [ "build_linux", "build_windows", "build_managed" ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Prepare env
|
||||
shell: bash
|
||||
run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: counterstrikesharp-build-windows-${{ env.GITHUB_SHA_SHORT }}
|
||||
path: build/windows
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: counterstrikesharp-build-linux-${{ env.GITHUB_SHA_SHORT }}
|
||||
path: build/linux
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: counterstrikesharp-build-api-${{ env.GITHUB_SHA_SHORT }}
|
||||
path: build/api
|
||||
|
||||
# TODO: This stuff should really be in a matrix
|
||||
- name: Add API to Artifacts
|
||||
run: |
|
||||
mkdir -p build/linux/addons/counterstrikesharp/api
|
||||
mkdir -p build/windows/addons/counterstrikesharp/api
|
||||
cp -r build/api/net7.0/publish/* build/linux/addons/counterstrikesharp/api
|
||||
cp -r build/api/net7.0/publish/* build/windows/addons/counterstrikesharp/api
|
||||
|
||||
- name: Zip Builds
|
||||
run: |
|
||||
(cd build/linux && zip -qq -r ../../counterstrikesharp-build-${{ needs.build_managed.outputs.buildnumber }}-linux-${{ env.GITHUB_SHA_SHORT }}.zip *)
|
||||
(cd build/windows && zip -qq -r ../../counterstrikesharp-build-${{ needs.build_managed.outputs.buildnumber }}-windows-${{ env.GITHUB_SHA_SHORT }}.zip *)
|
||||
|
||||
- name: Add dotnet runtime
|
||||
run: |
|
||||
mkdir -p build/output/addons/counterstrikesharp/dotnet
|
||||
mkdir -p build/linux/addons/counterstrikesharp/dotnet
|
||||
curl -s -L https://download.visualstudio.microsoft.com/download/pr/dc2c0a53-85a8-4fda-a283-fa28adb5fbe2/8ccade5bc400a5bb40cd9240f003b45c/aspnetcore-runtime-7.0.11-linux-x64.tar.gz \
|
||||
| tar xvz -C build/output/addons/counterstrikesharp/dotnet
|
||||
mv build/output/addons/counterstrikesharp/dotnet/shared/Microsoft.NETCore.App/7.0.11/* build/output/addons/counterstrikesharp/dotnet/shared/Microsoft.NETCore.App/
|
||||
| tar xvz -C build/linux/addons/counterstrikesharp/dotnet
|
||||
mv build/linux/addons/counterstrikesharp/dotnet/shared/Microsoft.NETCore.App/7.0.11/* build/linux/addons/counterstrikesharp/dotnet/shared/Microsoft.NETCore.App/
|
||||
|
||||
mkdir -p build/windows/addons/counterstrikesharp/dotnet
|
||||
curl -s -L https://download.visualstudio.microsoft.com/download/pr/a99861c8-2e00-4587-aaef-60366ca77307/a44ceec2c5d34165ae881600f52edc43/aspnetcore-runtime-7.0.11-win-x64.zip -o dotnet.zip
|
||||
unzip -qq dotnet.zip -d build/windows/addons/counterstrikesharp/dotnet
|
||||
|
||||
- name: Zip CounterStrikeSharp Runtime Build
|
||||
run: (cd build/output && zip -qq -r ../../counterstrikesharp-with-runtime-build-${{ env.BUILD_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}.zip *)
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: counterstrikesharp-with-runtime-build-${{ env.BUILD_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}
|
||||
path: build/output/
|
||||
- name: Zip Builds
|
||||
run: |
|
||||
(cd build/linux && zip -qq -r ../../counterstrikesharp-with-runtime-build-${{ needs.build_managed.outputs.buildnumber }}-linux-${{ env.GITHUB_SHA_SHORT }}.zip *)
|
||||
(cd build/windows && zip -qq -r ../../counterstrikesharp-with-runtime-build-${{ needs.build_managed.outputs.buildnumber }}-windows-${{ env.GITHUB_SHA_SHORT }}.zip *)
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: v${{ env.BUILD_NUMBER }}
|
||||
tag_name: v${{ needs.build_managed.outputs.buildnumber }}
|
||||
files: |
|
||||
counterstrikesharp-build-${{ env.BUILD_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}.zip
|
||||
counterstrikesharp-with-runtime-build-${{ env.BUILD_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}.zip
|
||||
counterstrikesharp-build-${{ needs.build_managed.outputs.buildnumber }}-windows-${{ env.GITHUB_SHA_SHORT }}.zip
|
||||
counterstrikesharp-with-runtime-build-${{ needs.build_managed.outputs.buildnumber }}-windows-${{ env.GITHUB_SHA_SHORT }}.zip
|
||||
counterstrikesharp-build-${{ needs.build_managed.outputs.buildnumber }}-linux-${{ env.GITHUB_SHA_SHORT }}.zip
|
||||
counterstrikesharp-with-runtime-build-${{ needs.build_managed.outputs.buildnumber }}-linux-${{ env.GITHUB_SHA_SHORT }}.zip
|
||||
|
||||
- name: Publish NuGet package
|
||||
run: |
|
||||
dotnet nuget push build/api/CounterStrikeSharp.API.1.0.${{ needs.build_managed.outputs.buildnumber }}.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
dotnet nuget push build/api/CounterStrikeSharp.API.1.0.${{ needs.build_managed.outputs.buildnumber }}.snupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
53
.github/workflows/pr-checks.yml
vendored
53
.github/workflows/pr-checks.yml
vendored
@@ -1,53 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: registry.gitlab.steamos.cloud/steamrt/sniper/sdk:latest
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
csharp:
|
||||
- managed/**/*
|
||||
- src/scripting/natives/**/*
|
||||
cpp:
|
||||
- src/**/*
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
if: steps.changes.outputs.csharp == 'true'
|
||||
with:
|
||||
dotnet-version: '7.0.x'
|
||||
|
||||
- if: steps.changes.outputs.csharp == 'true'
|
||||
run: dotnet build -c Release managed/CounterStrikeSharp.API
|
||||
|
||||
- name: Setup protobuf
|
||||
shell: bash
|
||||
if: steps.changes.outputs.cpp == 'true'
|
||||
run: sudo apt-get update && sudo apt install -y protobuf-compiler
|
||||
|
||||
- name: Configure CMake
|
||||
if: steps.changes.outputs.cpp == 'true'
|
||||
run: cmake -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
if: steps.changes.outputs.cpp == 'true'
|
||||
run: cmake --build build --config ${{env.BUILD_TYPE}}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -5,8 +5,13 @@ cmake-build-debug/
|
||||
.vscode/
|
||||
generated/
|
||||
|
||||
# configure_file auto generated.
|
||||
configs/addons/metamod/counterstrikesharp.vdf
|
||||
|
||||
libraries/mono/
|
||||
|
||||
CMakeSettings.json
|
||||
|
||||
build/
|
||||
build_test/
|
||||
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -17,3 +17,9 @@
|
||||
[submodule "libraries/GameTracking-CS2"]
|
||||
path = libraries/GameTracking-CS2
|
||||
url = https://github.com/SteamDatabase/GameTracking-CS2
|
||||
[submodule "libraries/DynoHook"]
|
||||
path = libraries/DynoHook
|
||||
url = git@github.com:qubka/DynoHook.git
|
||||
[submodule "libraries/asmjit"]
|
||||
path = libraries/asmjit
|
||||
url = git@github.com:asmjit/asmjit.git
|
||||
|
||||
196
CMakeLists.txt
196
CMakeLists.txt
@@ -1,99 +1,119 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
|
||||
Project(counterstrikesharp C CXX)
|
||||
# You must set ASM, otherwise CMAKE_ASM_COMPILE_OBJECT will not work on MSVC
|
||||
project(counterstrikesharp C CXX ASM)
|
||||
|
||||
include("makefiles/shared.cmake")
|
||||
|
||||
add_subdirectory(libraries/spdlog)
|
||||
add_subdirectory(libraries/dyncall)
|
||||
add_subdirectory(libraries/funchook)
|
||||
add_subdirectory(libraries/DynoHook)
|
||||
|
||||
set_property(TARGET dynohook PROPERTY DYNO_ARCH_X86 64)
|
||||
set_property(TARGET funchook-static PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
SET(SOURCE_FILES
|
||||
src/mm_plugin.cpp
|
||||
src/mm_plugin.h
|
||||
libraries/hl2sdk-cs2/tier1/convar.cpp
|
||||
libraries/hl2sdk-cs2/tier1/generichash.cpp
|
||||
libraries/hl2sdk-cs2/entity2/entitysystem.cpp
|
||||
libraries/hl2sdk-cs2/public/tier0/memoverride.cpp
|
||||
libraries/dotnet/hostfxr.h
|
||||
libraries/dotnet/coreclr_delegates.h
|
||||
"libraries/metamod-source/core/sourcehook/sourcehook.cpp"
|
||||
"libraries/metamod-source/core/sourcehook/sourcehook_impl_chookidman.cpp"
|
||||
"libraries/metamod-source/core/sourcehook/sourcehook_impl_chookmaninfo.cpp"
|
||||
"libraries/metamod-source/core/sourcehook/sourcehook_impl_cvfnptr.cpp"
|
||||
"libraries/metamod-source/core/sourcehook/sourcehook_impl_cproto.cpp"
|
||||
src/scripting/dotnet_host.h
|
||||
src/scripting/dotnet_host.cpp
|
||||
src/core/utils.h
|
||||
src/core/globals.h
|
||||
src/core/globals.cpp
|
||||
src/core/log.h
|
||||
src/core/log.cpp
|
||||
src/scripting/script_engine.h
|
||||
src/scripting/script_engine.cpp
|
||||
src/core/global_listener.h
|
||||
src/scripting/callback_manager.h
|
||||
src/scripting/callback_manager.cpp
|
||||
src/core/managers/event_manager.h
|
||||
src/core/managers/event_manager.cpp
|
||||
src/core/timer_system.h
|
||||
src/core/timer_system.cpp
|
||||
src/scripting/autonative.h
|
||||
src/scripting/natives/natives_engine.cpp
|
||||
src/core/engine_trace.h
|
||||
src/core/engine_trace.cpp
|
||||
src/scripting/natives/natives_callbacks.cpp
|
||||
src/core/managers/player_manager.h
|
||||
src/core/managers/player_manager.cpp
|
||||
src/scripting/natives/natives_vector.cpp
|
||||
src/scripting/natives/natives_timers.cpp
|
||||
src/utils/virtual.h
|
||||
src/scripting/natives/natives_events.cpp
|
||||
src/core/memory.cpp
|
||||
src/core/memory.h
|
||||
src/core/managers/con_command_manager.cpp
|
||||
src/core/managers/con_command_manager.h
|
||||
src/scripting/natives/natives_commands.cpp
|
||||
src/core/memory_module.h
|
||||
src/core/cs2_sdk/interfaces/cgameresourceserviceserver.h
|
||||
src/core/cs2_sdk/interfaces/cschemasystem.h
|
||||
src/core/cs2_sdk/interfaces/cs2_interfaces.h
|
||||
src/core/cs2_sdk/interfaces/cs2_interfaces.cpp
|
||||
src/core/cs2_sdk/schema.h
|
||||
src/core/cs2_sdk/schema.cpp
|
||||
src/core/function.cpp
|
||||
src/core/function.h
|
||||
src/scripting/natives/natives_memory.cpp
|
||||
src/scripting/natives/natives_schema.cpp
|
||||
src/scripting/natives/natives_entities.cpp
|
||||
src/core/managers/entity_manager.cpp
|
||||
src/core/managers/entity_manager.h
|
||||
src/core/managers/chat_manager.cpp
|
||||
src/core/managers/chat_manager.h
|
||||
src/core/managers/client_command_manager.cpp
|
||||
src/core/managers/client_command_manager.h
|
||||
src/core/managers/server_manager.cpp
|
||||
src/core/managers/server_manager.h
|
||||
src/scripting/natives/natives_server.cpp
|
||||
libraries/nlohmann/json.hpp
|
||||
src/mm_plugin.cpp
|
||||
src/mm_plugin.h
|
||||
libraries/hl2sdk-cs2/tier1/convar.cpp
|
||||
libraries/hl2sdk-cs2/tier1/generichash.cpp
|
||||
libraries/hl2sdk-cs2/entity2/entitysystem.cpp
|
||||
libraries/dotnet/hostfxr.h
|
||||
libraries/dotnet/coreclr_delegates.h
|
||||
libraries/metamod-source/core/sourcehook/sourcehook.cpp
|
||||
libraries/metamod-source/core/sourcehook/sourcehook_impl_chookidman.cpp
|
||||
libraries/metamod-source/core/sourcehook/sourcehook_impl_chookmaninfo.cpp
|
||||
libraries/metamod-source/core/sourcehook/sourcehook_impl_cvfnptr.cpp
|
||||
libraries/metamod-source/core/sourcehook/sourcehook_impl_cproto.cpp
|
||||
src/scripting/dotnet_host.h
|
||||
src/scripting/dotnet_host.cpp
|
||||
src/core/utils.h
|
||||
src/core/globals.h
|
||||
src/core/globals.cpp
|
||||
src/core/coreconfig.h
|
||||
src/core/coreconfig.cpp
|
||||
src/core/gameconfig.h
|
||||
src/core/gameconfig.cpp
|
||||
src/core/log.h
|
||||
src/core/log.cpp
|
||||
src/scripting/script_engine.h
|
||||
src/scripting/script_engine.cpp
|
||||
src/core/global_listener.h
|
||||
src/scripting/callback_manager.h
|
||||
src/scripting/callback_manager.cpp
|
||||
src/core/managers/event_manager.h
|
||||
src/core/managers/event_manager.cpp
|
||||
src/core/timer_system.h
|
||||
src/core/timer_system.cpp
|
||||
src/scripting/autonative.h
|
||||
src/scripting/natives/natives_engine.cpp
|
||||
src/core/engine_trace.h
|
||||
src/core/engine_trace.cpp
|
||||
src/scripting/natives/natives_callbacks.cpp
|
||||
src/core/managers/player_manager.h
|
||||
src/core/managers/player_manager.cpp
|
||||
src/scripting/natives/natives_vector.cpp
|
||||
src/scripting/natives/natives_timers.cpp
|
||||
src/utils/virtual.h
|
||||
src/scripting/natives/natives_events.cpp
|
||||
src/core/memory.cpp
|
||||
src/core/memory.h
|
||||
src/core/managers/con_command_manager.cpp
|
||||
src/core/managers/con_command_manager.h
|
||||
src/scripting/natives/natives_commands.cpp
|
||||
src/core/memory_module.h
|
||||
src/core/memory_module.cpp
|
||||
src/core/cs2_sdk/interfaces/cgameresourceserviceserver.h
|
||||
src/core/cs2_sdk/interfaces/cschemasystem.h
|
||||
src/core/cs2_sdk/interfaces/cs2_interfaces.h
|
||||
src/core/cs2_sdk/interfaces/cs2_interfaces.cpp
|
||||
src/core/cs2_sdk/schema.h
|
||||
src/core/cs2_sdk/schema.cpp
|
||||
src/core/function.cpp
|
||||
src/core/function.h
|
||||
src/scripting/natives/natives_memory.cpp
|
||||
src/scripting/natives/natives_schema.cpp
|
||||
src/scripting/natives/natives_entities.cpp
|
||||
src/core/managers/entity_manager.cpp
|
||||
src/core/managers/entity_manager.h
|
||||
src/core/managers/chat_manager.cpp
|
||||
src/core/managers/chat_manager.h
|
||||
src/core/managers/server_manager.cpp
|
||||
src/core/managers/server_manager.h
|
||||
src/scripting/natives/natives_server.cpp
|
||||
libraries/nlohmann/json.hpp
|
||||
src/scripting/natives/natives_dynamichooks.cpp
|
||||
)
|
||||
|
||||
|
||||
if (LINUX)
|
||||
# memoverride.cpp is not usable on CMake Windows, cuz CMake default link libraries (seems) always link ucrt.lib
|
||||
set(SOURCE_FILES
|
||||
${SOURCE_FILES}
|
||||
libraries/hl2sdk-cs2/public/tier0/memoverride.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
set(PROTO_DIRS -I${CMAKE_CURRENT_SOURCE_DIR}/libraries/GameTracking-CS2/Protobufs)
|
||||
file(GLOB PROTOS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/GameTracking-CS2/Protobufs/*.proto")
|
||||
|
||||
## Generate protobuf source & headers
|
||||
#add_custom_command(
|
||||
# OUTPUT protobuf_output_stamp
|
||||
# COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/src/protobuf/compile.sh
|
||||
# COMMENT "Generating protobuf files using compile.sh script"
|
||||
# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/protobuf
|
||||
# DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/protobuf/compile.sh
|
||||
# VERBATIM
|
||||
#)
|
||||
#
|
||||
#SET(SOURCE_FILES ${SOURCE_FILES} protobuf_output_stamp)
|
||||
if (LINUX)
|
||||
set(PROTOC_EXECUTABLE ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2/devtools/bin/linux/protoc)
|
||||
elseif(WIN32)
|
||||
set(PROTOC_EXECUTABLE ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2/devtools/bin/protoc.exe)
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT protobuf_output_stamp
|
||||
COMMAND ${PROTOC_EXECUTABLE} --proto_path=thirdparty/protobuf-3.21.8/src --proto_path=common --cpp_out=common common/network_connection.proto
|
||||
COMMENT "Generating protobuf file"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
SET(SOURCE_FILES ${SOURCE_FILES} protobuf_output_stamp)
|
||||
|
||||
# Sources
|
||||
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${NATIVES_SOURCES} ${CONVERSIONS_SOURCES} ${CONVERSIONS_HEADERS})
|
||||
@@ -105,15 +125,25 @@ target_include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/core/cs2_sdk
|
||||
)
|
||||
|
||||
include("makefiles/linux.base.cmake")
|
||||
if (LINUX)
|
||||
include("makefiles/linux.base.cmake")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
PREFIX ""
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/bin/linuxsteamrt64"
|
||||
)
|
||||
elseif(WIN32)
|
||||
include("makefiles/windows.base.cmake")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
PREFIX ""
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/bin/win64"
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
# Libraries
|
||||
target_link_libraries(${PROJECT_NAME} ${COUNTER_STRIKE_SHARP_LINK_LIBRARIES})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
PREFIX ""
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/bin/linuxsteamrt64"
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT_NAME} PRE_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
|
||||
34
README.md
34
README.md
@@ -2,32 +2,29 @@
|
||||
|
||||
CounterStrikeSharp is a server side modding framework for Counter-Strike: Global Offensive. This project attempts to implement a .NET Core scripting layer on top of a Metamod Source Plugin, allowing developers to create plugins that interact with the game server in a modern language (C#) to facilitate the creation of maintainable and testable code.
|
||||
|
||||
[Come and join our Discord](https://discord.gg/X7r3PmuYKq)
|
||||
|
||||
## History
|
||||
|
||||
This project is an ongoing migration of a previous project (titled [VSP.NET](https://github.com/roflmuffin/vspdotnet)) whereby a scripting layer was added to a Valve Server Plugin for CSGO.
|
||||
|
||||
Due to the architectural changes of CS2, the plugin is being rebuilt on the ground up, to support Linux 64-bit, something which was previously impossible.
|
||||
|
||||
## Philosophy
|
||||
|
||||
As a result, there are a few key philosophies and trade-offs that drive the project.
|
||||
- Only 64 bit is supported.
|
||||
- .NET only supports x64 on Linux; CSGO previously only supported 32 bit servers, but CS2 supports 64 bit on Linux.
|
||||
- Supporting both platforms is a lot of work for 1 person, so there are no real plans to support Windows.
|
||||
|
||||
## Install
|
||||
Development builds are currently available through GitHub actions, you can download the latest build from [there](https://github.com/roflmuffin/CounterStrikeSharp/actions/workflows/cmake-single-platform.yml).
|
||||
|
||||
Download the latest build from [here](https://github.com/roflmuffin/CounterStrikeSharp/releases). (Download the with runtime version if this is your first time installing).
|
||||
|
||||
Detailed installation instructions can be found in the [docs](https://docs.cssharp.dev/guides/getting-started/).
|
||||
|
||||
## What works?
|
||||
|
||||
_(Note, these were features in the previous VSP.NET project, but have not been implemented yet in this project)_
|
||||
|
||||
These features are the core of the platform and work pretty well/have a low risk of causing issues.
|
||||
|
||||
- [x] Console Commands, Server Commands (e.g. css_mycommand)
|
||||
- [x] Chat Commands with `!` and `/` prefixes (e.g. !mycommand)
|
||||
- [ ] **(In Progress)** Console Variables
|
||||
- [x] Console Commands, Server Commands (e.g. css_mycommand)
|
||||
- [x] Chat Commands with `!` and `/` prefixes (e.g. !mycommand)
|
||||
- [ ] **(In Progress)** Console Variables
|
||||
- [x] Game Event Handlers & Custom Events (e.g. player_death)
|
||||
- [x] Basic event value get/set (string, bool, int32, float)
|
||||
- [x] Complex event values get/set (ehandle, pawn, player controller)
|
||||
@@ -38,9 +35,10 @@ These features are the core of the platform and work pretty well/have a low risk
|
||||
- [x] OnMapStart
|
||||
- [x] OnTick
|
||||
- [x] Server Information (current map, game time, tick rate, model precaching)
|
||||
- [x] Schema System Access (access player values like current weapon, money, location etc.)
|
||||
- [x] Schema System Access (access player values like current weapon, money, location etc.)
|
||||
|
||||
## Links
|
||||
|
||||
- [Join the Discord](https://discord.gg/X7r3PmuYKq): Ask questions, provide suggestions
|
||||
- [Read the docs](https://docs.cssharp.dev/): Getting started guide, hello world plugin example
|
||||
- [Issue tracker](https://github.com/roflmuffin/CounterStrikeSharp/issues): Raise any issues here
|
||||
@@ -65,16 +63,20 @@ public class HelloWorldPlugin : BasePlugin
|
||||
|
||||
public override string ModuleVersion => "0.0.1";
|
||||
|
||||
public override string ModuleAuthor => "roflmuffin";
|
||||
|
||||
public override string ModuleDescription => "Simple hello world plugin";
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
Console.WriteLine("Hello World!");
|
||||
Logger.LogInformation("Plugin loaded successfully!");
|
||||
}
|
||||
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerConnect(EventPlayerConnect @event, GameEventInfo info)
|
||||
{
|
||||
// Userid will give you a reference to a CCSPlayerController class
|
||||
Log($"Player {@event.Userid.PlayerName} has connected!");
|
||||
Logger.LogInformation("Player {Name} has connected!", @event.Userid.PlayerName);
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
@@ -82,7 +84,7 @@ public class HelloWorldPlugin : BasePlugin
|
||||
[ConsoleCommand("issue_warning", "Issue warning to player")]
|
||||
public void OnCommand(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
Log("You shouldn't be doing that!");
|
||||
Logger.LogWarning("Player shouldn't be doing that");
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -94,7 +96,7 @@ I've also used the scripting context & native system that is implemented in Five
|
||||
|
||||
## How to Build
|
||||
|
||||
Building requires CMake on Linux.
|
||||
Building requires CMake.
|
||||
|
||||
Clone the repository
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"#css/admin": {
|
||||
"flags": [
|
||||
"@css/reservation",
|
||||
"@css/generic",
|
||||
"@css/kick",
|
||||
"@css/ban",
|
||||
"@css/unban",
|
||||
"@css/vip",
|
||||
"@css/slay",
|
||||
"@css/changemap",
|
||||
"@css/cvar",
|
||||
"@css/config",
|
||||
"@css/chat",
|
||||
"@css/vote",
|
||||
"@css/password",
|
||||
"@css/rcon",
|
||||
"@css/cheats",
|
||||
"@css/root"
|
||||
],
|
||||
"immunity": 100
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"example_command": {
|
||||
"flags": [
|
||||
"@css/custom-permission"
|
||||
],
|
||||
"check_type": "all",
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"Erikj": {
|
||||
"identity": "76561197960265731",
|
||||
"immunity": 100,
|
||||
"flags": [
|
||||
"@css/custom-flag-1",
|
||||
"@css/custom-flag-2"
|
||||
],
|
||||
"groups": [
|
||||
"#css/admin"
|
||||
],
|
||||
"command_overrides": {
|
||||
"css_plugins": true,
|
||||
"css": false
|
||||
}
|
||||
},
|
||||
"Another erikj": {
|
||||
"identity": "STEAM_0:1:1",
|
||||
"flags": ["@mycustomplugin/admin"]
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"Erikj": {
|
||||
"identity": "76561197960265731",
|
||||
"flags": [
|
||||
"@css/kick",
|
||||
"@css/ban"
|
||||
]
|
||||
},
|
||||
"Another erikj": {
|
||||
"identity": "STEAM_0:1:1",
|
||||
"flags": [
|
||||
"@anotherscope/foobar"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"PublicChatTrigger": [ "!" ],
|
||||
"SilentChatTrigger": [ "/" ],
|
||||
"FollowCS2ServerGuidelines": true,
|
||||
"PluginHotReloadEnabled": true
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Place your plugin configurations here.
|
||||
|
||||
TestPlugin/TestPlugin.json
|
||||
AnotherPlugin/AnotherPlugin.json
|
||||
@@ -2,46 +2,167 @@
|
||||
"UTIL_ClientPrintAll": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x48\\x89\\x5C\\x24\\x08\\x48\\x89\\x6C\\x24\\x10\\x48\\x89\\x74\\x24\\x18\\x57\\x48\\x81\\xEC\\x70\\x01\\x2A\\x2A\\x8B\\xE9",
|
||||
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x49\\x89\\xD7\\x41\\x56\\x49\\x89\\xF6\\x41\\x55\\x41\\x89\\xFD"
|
||||
}
|
||||
},
|
||||
"ClientPrint": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x48\\x85\\xC9\\x0F\\x84\\x2A\\x2A\\x2A\\x2A\\x48\\x8B\\xC4\\x48\\x89\\x58\\x18",
|
||||
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x49\\x89\\xCF\\x41\\x56\\x49\\x89\\xD6\\x41\\x55\\x41\\x89\\xF5\\x41\\x54\\x4C\\x8D\\xA5\\xA0\\xFE\\xFF\\xFF"
|
||||
}
|
||||
},
|
||||
"CCSPlayerController_SwitchTeam": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x40\\x56\\x57\\x48\\x81\\xEC\\x2A\\x2A\\x2A\\x2A\\x48\\x8B\\xF9\\x8B\\xF2\\x8B\\xCA",
|
||||
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x55\\x49\\x89\\xFD\\x89\\xF7"
|
||||
}
|
||||
},
|
||||
"CCSPlayerController_ChangeTeam": {
|
||||
"offsets": {
|
||||
"windows": 90,
|
||||
"linux": 89
|
||||
}
|
||||
},
|
||||
"CCSPlayerController_Respawn": {
|
||||
"offsets": {
|
||||
"windows": 241,
|
||||
"linux": 243
|
||||
}
|
||||
},
|
||||
"CCSPlayerPawn_Respawn": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x40\\x53\\x48\\x83\\xEC\\x20\\x8B\\x91\\x38\\x0B\\x00\\x00\\x48\\x8B\\xD9",
|
||||
"linux": "\\x8B\\x8F\\x40\\x0E\\x00\\x00\\x83\\xF9\\xFF\\x0F\\x84\\xD9\\x01"
|
||||
}
|
||||
},
|
||||
"CCSPlayerPawnBase_PostThink": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x48\\x8B\\xC4\\x48\\x89\\x48\\x08\\x55\\x53\\x56\\x57\\x41\\x56\\x48\\x8D\\xA8\\xD8\\xFE\\xFF\\xFF",
|
||||
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x49\\x89\\xFF\\x41\\x56\\x41\\x55\\x41\\x54\\x53\\x48\\x81\\xEC\\x2A\\x2A\\x2A\\x2A\\xE8\\x2A\\x2A\\x2A\\x2A\\x4C"
|
||||
}
|
||||
},
|
||||
"GiveNamedItem": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x48\\x89\\x5C\\x24\\x18\\x48\\x89\\x74\\x24\\x20\\x55\\x57\\x41\\x54\\x41\\x56\\x41\\x57\\x48\\x8D\\x6C\\x24\\xD9",
|
||||
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x49\\x89\\xCE\\x41\\x55\\x49\\x89\\xF5\\x41\\x54\\x49\\x89\\xD4"
|
||||
}
|
||||
},
|
||||
"UTIL_Remove": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x48\\x85\\xC9\\x74\\x2A\\x48\\x8B\\xD1\\x48\\x8B\\x0D\\x2A\\x2A\\x2A\\x2A",
|
||||
"linux": "\\x48\\x89\\xFE\\x48\\x85\\xFF\\x74\\x2A\\x48\\x8D\\x05\\x2A\\x2A\\x2A\\x2A\\x48"
|
||||
}
|
||||
},
|
||||
"Host_Say": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x44\\x89\\x4C\\x24\\x20\\x44\\x88\\x44\\x24\\x18",
|
||||
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x49\\x89\\xFF\\x41\\x56\\x41\\x55\\x41\\x54\\x4D\\x89\\xC4"
|
||||
}
|
||||
},
|
||||
"CBaseModelEntity_SetModel": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x48\\x89\\x5C\\x24\\x10\\x48\\x89\\x7C\\x24\\x20\\x55\\x48\\x8B\\xEC\\x48\\x83\\xEC\\x50",
|
||||
"linux": "\\x55\\x48\\x89\\xF2\\x48\\x89\\xE5\\x41\\x54\\x49\\x89\\xFC\\x48\\x8D\\x2A\\x2A\\x48\\x83\\x2A\\x2A\\x2A\\x8D\\x05\\x2A\\x2A\\x2A\\x00\\x48\\x8B\\x30\\x48\\x8B\\x06\\xFF\\x2A\\x2A\\x48\\x8B\\x45\\x2A\\x48\\x8D\\x2A\\x2A\\x4C\\x89\\x2A\\x48\\x89\\x45\\x2A\\x2A\\x68\\xFC"
|
||||
}
|
||||
},
|
||||
"CCSPlayer_ItemServices_DropActivePlayerWeapon": {
|
||||
"offsets": {
|
||||
"windows": 18,
|
||||
"linux": 19
|
||||
}
|
||||
},
|
||||
"CCSPlayer_ItemServices_RemoveWeapons": {
|
||||
"offsets": {
|
||||
"windows": 21,
|
||||
"linux": 20
|
||||
}
|
||||
},
|
||||
"CGameSceneNode_GetSkeletonInstance": {
|
||||
"offsets": {
|
||||
"windows": 8,
|
||||
"linux": 8
|
||||
}
|
||||
},
|
||||
"CCSGameRules_TerminateRound": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x48\\x8B\\xC4\\x4C\\x89\\x48\\x20\\x55\\x56",
|
||||
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x41\\x55\\x49\\x89\\xFD\\x41\\x54\\x53\\x48\\x81\\xEC\\xE8"
|
||||
}
|
||||
},
|
||||
"UTIL_CreateEntityByName": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x48\\x83\\xEC\\x48\\xC6\\x44\\x24\\x30\\x00\\x4C\\x8B\\xC1",
|
||||
"linux": "\\x48\\x8D\\x05\\x2A\\x2A\\x2A\\x2A\\x55\\x48\\x89\\xFA"
|
||||
}
|
||||
},
|
||||
"CBaseEntity_DispatchSpawn": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x48\\x89\\x5C\\x24\\x10\\x57\\x48\\x83\\xEC\\x30\\x48\\x8B\\xDA\\x48\\x8B\\xF9\\x48\\x85\\xC9",
|
||||
"linux": "\\x48\\x85\\xFF\\x74\\x4B\\x55\\x48\\x89\\xE5\\x41\\x56"
|
||||
}
|
||||
},
|
||||
"LegacyGameEventListener": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x48\\x8B\\x15\\x2A\\x2A\\x2A\\x2A\\x48\\x85\\xD2\\x74\\x2A\\x85\\xC9\\x74",
|
||||
"linux": "\\x48\\x8B\\x05\\x2A\\x2A\\x2A\\x2A\\x48\\x85\\xC0\\x74\\x2A\\x83\\xFF\\x3F\\x76\\x2A\\x31\\xC0"
|
||||
}
|
||||
},
|
||||
"CBasePlayerPawn_CommitSuicide": {
|
||||
"offsets": {
|
||||
"linux": 355
|
||||
"windows": 356,
|
||||
"linux": 356
|
||||
}
|
||||
},
|
||||
"CBaseEntity_Teleport": {
|
||||
"offsets": {
|
||||
"windows": 148,
|
||||
"linux": 147
|
||||
}
|
||||
},
|
||||
"CBaseEntity_TakeDamageOld": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x40\\x56\\x57\\x48\\x83\\xEC\\x58\\x48\\x8B\\x41\\x10",
|
||||
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x41\\x55\\x41\\x54\\x49\\x89\\xFC\\x53\\x48\\x83\\xEC\\x38\\x4C\\x8D\\x2D\\x2A\\x2A\\x2A\\x2A\\x49\\x8B\\x7D\\x00\\x48\\x85\\xFF\\x0F\\x84\\x2A\\x2A\\x2A\\x2A"
|
||||
}
|
||||
},
|
||||
"CBaseTrigger_StartTouch": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x41\\x56\\x41\\x57\\x48\\x83\\xEC\\x58\\x48\\x8B\\x01",
|
||||
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x56\\x49\\x89\\xF6\\x41\\x55\\x49\\x89\\xFD\\x41\\x54\\x53\\xBB"
|
||||
}
|
||||
},
|
||||
"CBaseTrigger_EndTouch": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x40\\x53\\x57\\x41\\x55\\x48\\x83\\xEC\\x40",
|
||||
"linux": "\\x55\\xBA\\xFF\\xFF\\xFF\\xFF\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x41\\x55\\x49"
|
||||
}
|
||||
},
|
||||
"GameEntitySystem": {
|
||||
"offsets": {
|
||||
"windows": 88,
|
||||
"linux": 80
|
||||
}
|
||||
},
|
||||
"GameEventManager": {
|
||||
"offsets": {
|
||||
"windows": 91,
|
||||
"linux": 91
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
"Metamod Plugin"
|
||||
{
|
||||
"alias" "counterstrikesharp"
|
||||
"file" "addons/counterstrikesharp/bin/linuxsteamrt64/counterstrikesharp"
|
||||
}
|
||||
@@ -36,6 +36,10 @@ export default defineConfig({
|
||||
label: 'Features',
|
||||
autogenerate: { directory: 'features' },
|
||||
},
|
||||
{
|
||||
label: 'Admin Framework',
|
||||
autogenerate: { directory: 'admin-framework' },
|
||||
},
|
||||
{
|
||||
label: 'Reference',
|
||||
autogenerate: { directory: 'reference' },
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Admin Command Attributes
|
||||
description: A guide on using the Admin Command Attributes in plugins.
|
||||
---
|
||||
|
||||
## Assigning permissions to a Command
|
||||
|
||||
Assigning permissions to a Command is as easy as tagging the Command method (function callback) with either a `RequiresPermissions` or `RequiresPermissionsOr` attribute. The difference between the two attributes is that `RequiresPermissionsOr` needs only one permission to be present on the caller to be passed, while `RequiresPermissions` needs the caller to have all permissions listed. CounterStrikeSharp handles all of the permission checks behind the scenes for you.
|
||||
|
||||
```csharp
|
||||
[RequiresPermissions("@css/slay", "@custom/permission")]
|
||||
public void OnMyCommand(CCSPlayerController? caller, CommandInfo info)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
[RequiresPermissionsOr("@css/ban", "@custom/permission-2")]
|
||||
public void OnMyOtherCommand(CCSPlayerController? caller, CommandInfo info)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
You can even stack the attributes on top of each other, and all of them will be checked.
|
||||
|
||||
```csharp
|
||||
// Requires (@css/cvar AND @custom/permission-1) AND either (@custom/permission-1 OR @custom/permission-2).
|
||||
[RequiresPermissions("@css/cvar", "@custom/permission-1")]
|
||||
[RequiresPermissionsOr("@css/ban", "@custom/permission-2")]
|
||||
public void OnMyComplexCommand(CCSPlayerController? caller, CommandInfo info)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
You can also check for groups using the same attributes.
|
||||
|
||||
```csharp
|
||||
[RequiresPermissions("#css/simple-admin")]
|
||||
public void OnMyGroupCommand(CCSPlayerController? caller, CommandInfo info)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
61
docs/src/content/docs/admin-framework/defining-admins.md
Normal file
61
docs/src/content/docs/admin-framework/defining-admins.md
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: Defining Admins
|
||||
description: A guide on how to define admins for CounterStrikeSharp.
|
||||
---
|
||||
|
||||
## Admin Framework
|
||||
|
||||
CounterStrikeSharp has a basic framework which allows plugin developers to assign permissions to commands. When CSS is initialized, a list of admins are loaded from `configs/admins.json`.
|
||||
|
||||
## Adding Admins
|
||||
|
||||
Adding an Admin is as simple as creating a new entry in the `configs/admins.json` file. The important things you need to declare are the SteamID identifier and the permissions they have. CounterStrikeSharp will do all the heavy-lifting to decipher your SteamID. If you're familiar with SourceMod, permission definitions are slightly different as they're defined by an array of strings instead of a string of characters.
|
||||
|
||||
```json
|
||||
{
|
||||
"ZoNiCaL": {
|
||||
"identity": "76561198808392634",
|
||||
"flags": ["@css/changemap", "@css/generic"]
|
||||
},
|
||||
"another ZoNiCaL": {
|
||||
"identity": "STEAM_0:1:1",
|
||||
"flags": ["@css/generic"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also manually assign permissions to players in code with `AdminManager.AddPlayerPermissions` and `AdminManager.RemovePlayerPermissions`. These changes are not saved to `configs/admins.json`.
|
||||
|
||||
:::note
|
||||
All user permissions MUST start with an at-symbol @ character, otherwise CounterStrikeSharp will not recognize the permission.
|
||||
:::
|
||||
|
||||
### Standard Permissions
|
||||
|
||||
Because the flag system is just a list of strings associated with a user, there is no real list of letter based flags like there was previously in something like SourceMod. This means as a plugin author you can declare your own flags, scoped with an `@` symbol, like `@roflmuffin/guns`, which might be the permission to allow spawning of guns in a given command.
|
||||
|
||||
However there is a somewhat standardized list of flags that it is advised you use if you are adding functionality that aligns with their purpose, and these are based on the original SourceMod flags:
|
||||
|
||||
```shell
|
||||
@css/reservation # Reserved slot access.
|
||||
@css/generic # Generic admin.
|
||||
@css/kick # Kick other players.
|
||||
@css/ban # Ban other players.
|
||||
@css/unban # Remove bans.
|
||||
@css/vip # General vip status.
|
||||
@css/slay # Slay/harm other players.
|
||||
@css/changemap # Change the map or major gameplay features.
|
||||
@css/cvar # Change most cvars.
|
||||
@css/config # Execute config files.
|
||||
@css/chat # Special chat privileges.
|
||||
@css/vote # Start or create votes.
|
||||
@css/password # Set a password on the server.
|
||||
@css/rcon # Use RCON commands.
|
||||
@css/cheats # Change sv_cheats or use cheating commands.
|
||||
@css/root # Magically enables all flags and ignores immunity values.
|
||||
```
|
||||
|
||||
:::note
|
||||
CounterStrikeSharp does not implement traditional admin command such as `!slay`, `!kick`, and `!ban`. It is up to individual plugins to implement these commands.
|
||||
:::
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: Defining Command Overrides
|
||||
description: A guide on how to define command overrides for CounterStrikeSharp.
|
||||
---
|
||||
|
||||
## Defining Admin and Group specific overrides
|
||||
|
||||
Command permissions can be overriden so specific admins or groups can execute the command, regardless of any permissions they may or may not have. You can define command overrides by adding the `command_overrides` key to each admin in `configs/admins.json` or group in `configs/admin_groups.json`. Command overrides can either be set to `true`, meaning the admin/group can execute the command, or `false`, meaning the admin/group cannot execute the command at all.
|
||||
|
||||
```json
|
||||
{
|
||||
"ZoNiCaL": {
|
||||
"identity": "76561198808392634",
|
||||
"flags": ["@css/changemap", "@css/generic"],
|
||||
"immunity": 100,
|
||||
"command_overrides": {
|
||||
"example_command": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
"#css/simple-admin": {
|
||||
"flags": [
|
||||
"@css/generic",
|
||||
"@css/reservation",
|
||||
"@css/ban",
|
||||
"@css/slay",
|
||||
],
|
||||
"command_overrides": {
|
||||
"example_command_2": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can set a command override for a player in code using `AdminManager.SetPlayerCommandOverride`.
|
||||
|
||||
## Replacing Command permissions
|
||||
|
||||
Command permissions can be entirely replaced. These are defined in `configs/admin_overrides.json`. The important things you need to declare are what commands are being changed and what their new flags are. Command overrides can be set to be enabled or disabled, and you can toggle them in code with `AdminManager.SetCommandOverrideState`. You can also specify whether the command override requires the caller to have all of the new permissions (similar to a `RequiresPermissions` attribute check) or only one or more permissions (similar to a `RequirePermissionsOr` attribute check). You cannot stack permission checks.
|
||||
|
||||
```json
|
||||
"css": {
|
||||
"flags": [
|
||||
"@css/custom-permission"
|
||||
],
|
||||
"check_type": "all",
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
You can check if a command has been overriden in code using `AdminManager.CommandIsOverriden`, and you can manipulate the command override permissions using `AdminManager.AddPermissionOverride` and `AdminManager.RemovePermissionOverride`.
|
||||
42
docs/src/content/docs/admin-framework/defining-groups.md
Normal file
42
docs/src/content/docs/admin-framework/defining-groups.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Defining Admin Groups
|
||||
description: A guide on how to define admin groups for CounterStrikeSharp.
|
||||
---
|
||||
|
||||
## Adding Groups
|
||||
|
||||
Groups can be created to group a series of permissions together under one tag. They are defined in `configs/admin_groups.json`. The important things you need to declare is the name of the group and the permissions they have.
|
||||
|
||||
```json
|
||||
"#css/simple-admin": {
|
||||
"flags": [
|
||||
"@css/generic",
|
||||
"@css/reservation",
|
||||
"@css/ban",
|
||||
"@css/slay",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can add admins to groups using the `groups` array in `configs/admins.json`
|
||||
```json
|
||||
{
|
||||
"erikj": {
|
||||
"identity": "76561198808392634",
|
||||
"flags": ["@mycustomplugin/admin"],
|
||||
"groups": ["#css/simple-admin"]
|
||||
},
|
||||
"Another erikj": {
|
||||
"identity": "STEAM_0:1:1",
|
||||
"flags": ["@mycustomplugin/admin"],
|
||||
"groups": ["#css/simple-admin"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
All group names MUST start with a hashtag # character, otherwise CounterStrikeSharp won't recognize the group.
|
||||
:::
|
||||
|
||||
Admins can be assigned to multiple groups and they will inherit their flags. You can manually assign groups to players in code with `AdminManager.AddPlayerToGroup` and `AdminManager.RemovePlayerFromGroup`.
|
||||
|
||||
36
docs/src/content/docs/admin-framework/defining-immunity.md
Normal file
36
docs/src/content/docs/admin-framework/defining-immunity.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Defining Admin Immunity
|
||||
description: A guide on how to define immunity for admins for CounterStrikeSharp.
|
||||
---
|
||||
|
||||
## Player Immunity
|
||||
|
||||
Admins can be assigned an immunity value, similar to SourceMod. If an admin or player with a lower immunity value targets another admin or player with a larger immunity value, then the command will fail. You can define an immunity value by adding the `immunity` key to each admin in `configs/admins.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"ZoNiCaL": {
|
||||
"identity": "76561198808392634",
|
||||
"flags": ["@css/changemap", "@css/generic"],
|
||||
"immunity": 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can even assign an immunity value to groups. If an admin has a lower immunity value, the group's value will be used instead.
|
||||
|
||||
```json
|
||||
"#css/simple-admin": {
|
||||
"flags": [
|
||||
"@css/generic",
|
||||
"@css/reservation",
|
||||
"@css/ban",
|
||||
"@css/slay",
|
||||
],
|
||||
"immunity": 100
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
CounterStrikeSharp does not automatically handle immunity checking. It is up to individual plugins to handle immunity checks as they can implement different ways of targeting players. This can be done in code with `AdminManager.CanPlayerTarget`. You can also set a player's immunity in code with `AdminManager.SetPlayerImmunity`.
|
||||
:::
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
title: Admin Framework
|
||||
description: A guide on using the Admin Framework in plugins.
|
||||
---
|
||||
|
||||
## Admin Framework
|
||||
|
||||
CounterStrikeSharp has a basic framework which allows plugin developers to assign permissions to commands. When CSS is initialized, a list of admins are loaded from `configs/admins.json`.
|
||||
|
||||
## Adding Admins
|
||||
|
||||
Adding an Admin is as simple as creating a new entry in the `configs/admins.json` file. The important things you need to declare are the SteamID identifier and the permissions they have. CounterStrikeSharp will do all the heavy-lifting to decipher your SteamID. If you're familar with SourceMod, permission definitions are slightly different as they're defined by an array of strings instead of a string of characters.
|
||||
|
||||
```json
|
||||
{
|
||||
"ZoNiCaL": {
|
||||
"identity": "76561198808392634",
|
||||
"flags": ["can_manipulate_players", "admin_messages"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also manually assign permissions to players in code with `AddPlayerPermissions` and `RemovePlayerPermissions`. These changes are not saved to `configs/admins.json`.
|
||||
|
||||
## Assigning permissions to a Command
|
||||
|
||||
Assigning permissions to a Command is as easy as tagging the Command method (function callback) with a `RequiresPermissions` attribute.
|
||||
|
||||
```csharp
|
||||
[RequiresPermissions("can_execute_test_command", "other_permission")]
|
||||
public void OnMyCommand(CCSPlayerController? caller, CommandInfo info)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
CounterStrikeSharp handles all of the permission checks behind the scenes for you.
|
||||
@@ -80,10 +80,17 @@ public void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
|
||||
|
||||
If a client tries to execute the command without the `[target]` argument, it will print a message to them in chat:
|
||||
|
||||
> [CSS] Expected usage: "!freeze [target]".
|
||||
> If a command is executed by the wrong user, it will print a message to them:
|
||||
> [CSS] This command can only be executed by clients.
|
||||
> Valid `CommandUsage` values:
|
||||
```shell
|
||||
[CSS] Expected usage: "!freeze [target]".
|
||||
```
|
||||
|
||||
If a command is executed by the wrong user, it will print a message to them:
|
||||
|
||||
```shell
|
||||
[CSS] This command can only be executed by clients.
|
||||
```
|
||||
|
||||
Valid `CommandUsage` values:
|
||||
|
||||
```csharp
|
||||
public enum CommandUsage
|
||||
|
||||
@@ -17,8 +17,13 @@ The first parameter type must be a subclass of the `GameEvent` class. The names
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerConnect(EventPlayerConnect @event, GameEventInfo info)
|
||||
{
|
||||
// Userid will give you a reference to a CCSPlayerController class
|
||||
Log($"Player {@event.Userid.PlayerName} has connected!");
|
||||
// Userid will give you a reference to a CCSPlayerController class.
|
||||
// Before accessing any of its fields, you must first check if the Userid
|
||||
// handle is actually valid, otherwise you may run into runtime exceptions.
|
||||
// See the documentation section on Referencing Players for details.
|
||||
if (@event.Userid.IsValid) {
|
||||
Logger.LogInformation("Player {Name} has connected!", @event.Userid.PlayerName);
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
@@ -33,7 +38,7 @@ public override void Load(bool hotReload)
|
||||
{
|
||||
RegisterEventHandler<EventRoundStart>((@event, info) =>
|
||||
{
|
||||
Console.WriteLine($"Round has started with time limit of {@event.Timelimit}");
|
||||
Logger.LogInformation("Round has started with time limit of {Timelimit}", @event.Timelimit);
|
||||
|
||||
return HookResult.Continue;
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ public override void Load(bool hotReload)
|
||||
projectile.SmokeColor.X = Random.Shared.NextSingle() * 255.0f;
|
||||
projectile.SmokeColor.Y = Random.Shared.NextSingle() * 255.0f;
|
||||
projectile.SmokeColor.Z = Random.Shared.NextSingle() * 255.0f;
|
||||
Log($"Smoke grenade spawned with color {projectile.SmokeColor}");
|
||||
Logger.LogInformation("Smoke grenade spawned with color {SmokeColor}", projectile.SmokeColor);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
71
docs/src/content/docs/guides/dependency-injection.md
Normal file
71
docs/src/content/docs/guides/dependency-injection.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Dependency Injection
|
||||
description: How to make use of dependency injection in CounterStrikeSharp
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
`CounterStrikeSharp` uses a standard <a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0" target="_blank">`IServiceCollection`</a> to allow for dependency injection in plugins.
|
||||
|
||||
There are a handful of standard services that are predefined for you (`ILogger` for logging for instance), with more to come in the future. To add your own scoped & singleton services to the container, you can create a new class that implements the `IPluginServiceCollection<T>` interface for your plugin.
|
||||
|
||||
```csharp
|
||||
public class TestPlugin : BasePlugin
|
||||
{
|
||||
// Plugin code...
|
||||
}
|
||||
|
||||
public class TestPluginServiceCollection : IPluginServiceCollection<TestPlugin>
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddScoped<ExampleInjectedClass>();
|
||||
serviceCollection.AddLogging(builder => ...);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
CounterStrikeSharp will search your assembly for any implementations of `IPlugin` and then any implementations of `IPluginServiceCollection<T>` where `T` is your plugin. It will then configure the service provider and then request a singleton instance of your plugin before proceeding to the load step.
|
||||
|
||||
In this way, any dependencies that are listed in your plugin class constructor will automatically get injected at instantation time (before load).
|
||||
|
||||
### Example
|
||||
|
||||
```csharp
|
||||
public class TestInjectedClass
|
||||
{
|
||||
private readonly ILogger<TestInjectedClass> _logger;
|
||||
|
||||
public TestInjectedClass(ILogger<TestInjectedClass> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Hello()
|
||||
{
|
||||
_logger.LogInformation("Hello World from Test Injected Class");
|
||||
}
|
||||
}
|
||||
|
||||
public class TestPluginServiceCollection : IPluginServiceCollection<SamplePlugin>
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddScoped<TestInjectedClass>();
|
||||
}
|
||||
}
|
||||
|
||||
public class SamplePlugin : BasePlugin
|
||||
{
|
||||
private readonly TestInjectedClass _testInjectedClass;
|
||||
public SamplePlugin(TestInjectedClass testInjectedClass)
|
||||
{
|
||||
_testInjectedClass = testInjectedClass;
|
||||
}
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
_testInjectedClass.Hello();
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: How to get started installing & using CounterStrikeSharp.
|
||||
sidebar:
|
||||
order: 0
|
||||
---
|
||||
|
||||
# Installation
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: Hello World Plugin
|
||||
description: How to write your first plugin for CounterStrikeSharp
|
||||
sidebar:
|
||||
order: 0
|
||||
---
|
||||
|
||||
## Creating a New Project
|
||||
@@ -34,6 +36,13 @@ Use your IDE (Visual Studio/Rider) to add a reference to the `CounterStrikeSharp
|
||||
</Project>
|
||||
```
|
||||
|
||||
:::tip
|
||||
Instead of manually adding a reference to `CounterStrikeSharp.Api.dll`, you can install the NuGet package `CounterStrikeSharp.Api` using the following:
|
||||
```shell
|
||||
dotnet add package CounterStrikeSharp.API
|
||||
```
|
||||
:::
|
||||
|
||||
### Creating a Plugin File
|
||||
|
||||
Rename the default class file that came with your new project (by default it should be `Class1.cs`) to something more accurate, like `HelloWorldPlugin.cs`. Inside this file, we will insert the stub hello world plugin. Be sure to change the name and namespace so it matches your project name.
|
||||
@@ -59,19 +68,20 @@ Now build your project using your ide or the `dotnet build` command. You should
|
||||
|
||||
### Installing your Plugin
|
||||
|
||||
Locate the `plugins` folder in your CS2 dedicated server (`/game/csgo/addons/counterstrikesharp/plugins`) and create a new folder with the exact same name as your output .dll file. In this example it would be `HelloWorldPlugin.dll`, so I will make a new folder called `HelloWorldPlugin`. Inside of this folder, copy and paste all of the `.dll` files _except_ the `CounterStrikeSharp.API.dll` file. Once completed, the folder should look as follows:
|
||||
Locate the `plugins` folder in your CS2 dedicated server (`/game/csgo/addons/counterstrikesharp/plugins`) and create a new folder with the exact same name as your output .dll file. In this example it would be `HelloWorldPlugin.dll`, so I will make a new folder called `HelloWorldPlugin`. Inside of this folder, copy and paste the: `HelloWorldPlugin.deps.json`, `HelloWorldPlugin.dll`, and `HelloWorldPlugin.pdb` files. Once completed, the folder should look as follows:
|
||||
|
||||
```shell
|
||||
.
|
||||
└── SamplePlugin
|
||||
├── McMaster.NETCore.Plugins.dll
|
||||
├── Microsoft.DotNet.PlatformAbstractions.dll
|
||||
├── Microsoft.Extensions.DependencyModel.dll
|
||||
├── SamplePlugin.deps.json
|
||||
├── SamplePlugin.dll
|
||||
└── SamplePlugin.pdb
|
||||
└── HelloWorldPlugin
|
||||
├── HelloWorldPlugin.deps.json
|
||||
├── HelloWorldPlugin.dll
|
||||
└── HelloWorldPlugin.pdb
|
||||
```
|
||||
|
||||
:::caution
|
||||
If you have installed external nuget packages for your plugin, you may need to include their respective `.dll`s. For example, if you utilize the `Stateless` C# library, include `stateless.dll` in your `HelloWorld` plugin directory.
|
||||
:::
|
||||
|
||||
:::note
|
||||
Note that some of these dependencies may change depending on the version of CounterStrikeSharp being used.
|
||||
:::
|
||||
|
||||
@@ -25,16 +25,20 @@ public class HelloWorldPlugin : BasePlugin
|
||||
|
||||
public override string ModuleVersion => "0.0.1";
|
||||
|
||||
public override string ModuleAuthor => "roflmuffin";
|
||||
|
||||
public override string ModuleDescription => "Simple hello world plugin";
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
Console.WriteLine("Hello World!");
|
||||
Logger.LogInformation("Plugin loaded successfully!");
|
||||
}
|
||||
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerConnect(EventPlayerConnect @event, GameEventInfo info)
|
||||
{
|
||||
// Userid will give you a reference to a CCSPlayerController class
|
||||
Log($"Player {@event.Userid.PlayerName} has connected!");
|
||||
Logger.LogInformation("Player {Name} has connected!", @event.Userid.PlayerName);
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
@@ -42,7 +46,7 @@ public class HelloWorldPlugin : BasePlugin
|
||||
[ConsoleCommand("issue_warning", "Issue warning to player")]
|
||||
public void OnCommand(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
Log("You shouldn't be doing that!");
|
||||
Logger.LogWarning("Player shouldn't be doing that");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
30
docs/src/content/docs/reference/core-configuration.md
Normal file
30
docs/src/content/docs/reference/core-configuration.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: Core Configuration
|
||||
description: Summary for core configuration values
|
||||
---
|
||||
|
||||
## PublicChatTrigger
|
||||
|
||||
List of characters to use for public chat triggers.
|
||||
|
||||
## SilentChatTrigger
|
||||
|
||||
List of characters to use for silent chat triggers.
|
||||
|
||||
## FollowCS2ServerGuidelines
|
||||
|
||||
Per [CS2 Server Guidelines](https://blog.counter-strike.net/index.php/server_guidelines/), certain plugin
|
||||
functionality will trigger all of the game server owner's Game Server Login Tokens
|
||||
(GSLTs) to get banned when executed on a Counter-Strike 2 game server.
|
||||
|
||||
Enabling this option will block plugins from using functionality that is known to cause this.
|
||||
This option only has any effect on CS2. Note that this does NOT guarantee that you cannot
|
||||
receive a ban.
|
||||
|
||||
:::note
|
||||
Disable this option at your own risk.
|
||||
:::
|
||||
|
||||
## PluginHotReloadEnabled
|
||||
|
||||
When enabled, plugins are automatically reloaded when their .dll file is updated.
|
||||
12
examples/HelloWorld/HelloWorld.csproj
Normal file
12
examples/HelloWorld/HelloWorld.csproj
Normal file
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
24
examples/HelloWorld/HelloWorldPlugin.cs
Normal file
24
examples/HelloWorld/HelloWorldPlugin.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HelloWorld;
|
||||
|
||||
[MinimumApiVersion(80)]
|
||||
public class HelloWorldPlugin : BasePlugin
|
||||
{
|
||||
public override string ModuleName => "Example: Hello World";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
|
||||
public override string ModuleDescription => "A simple plugin that says hello world!";
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
Logger.LogInformation("Hello World! We are loading!");
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
Logger.LogInformation("Hello World! We are unloading!");
|
||||
}
|
||||
}
|
||||
2
examples/HelloWorld/README.md
Normal file
2
examples/HelloWorld/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Hello World
|
||||
This is a minimal "Hello World" example that executes code on load & unload.
|
||||
@@ -73,7 +73,7 @@ namespace WarcraftPlugin
|
||||
var victim = @event.Userid;
|
||||
var headshot = @event.Headshot;
|
||||
|
||||
if (attacker.IsValid && victim.IsValid && (attacker.EntityIndex.Value.Value != victim.EntityIndex.Value.Value) && !attacker.IsBot)
|
||||
if (attacker.IsValid && victim.IsValid && (attacker != victim) && !attacker.IsBot)
|
||||
{
|
||||
var weaponName = attacker.PlayerPawn.Value.WeaponServices.ActiveWeapon.Value.DesignerName;
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
|
||||
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
|
||||
4
examples/WithCommands/README.md
Normal file
4
examples/WithCommands/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# With Commands
|
||||
This is an example that shows how to register chat & console commands.
|
||||
|
||||
All commands that are prefixed with "css_" will automatically be registered as a chat command without the prefix. i.e. `css_ping` can be called with `!ping` or `/ping`.
|
||||
12
examples/WithCommands/WithCommands.csproj
Normal file
12
examples/WithCommands/WithCommands.csproj
Normal file
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
63
examples/WithCommands/WithCommandsPlugin.cs
Normal file
63
examples/WithCommands/WithCommandsPlugin.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
|
||||
namespace WithCommands;
|
||||
|
||||
[MinimumApiVersion(80)]
|
||||
public class WithCommandsPlugin : BasePlugin
|
||||
{
|
||||
public override string ModuleName => "Example: With Commands";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
|
||||
public override string ModuleDescription => "A simple plugin that registers some commands";
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
// All commands that are prefixed with "css_" will automatically be registered as a chat command without the prefix.
|
||||
// i.e. `css_ping` can be called with `!ping` or `/ping`.
|
||||
// Commands can be registered using the instance `AddCommand` method.
|
||||
AddCommand("css_ping", "Responds to the caller with \"pong\"", (player, commandInfo) =>
|
||||
{
|
||||
// The player is null, then the command has been called by the server console.
|
||||
if (player == null)
|
||||
{
|
||||
commandInfo.ReplyToCommand("pong server");
|
||||
return;
|
||||
}
|
||||
|
||||
commandInfo.ReplyToCommand("pong");
|
||||
});
|
||||
}
|
||||
|
||||
// Commands can also be registered using the `Command` attribute.
|
||||
[ConsoleCommand("css_hello", "Responds to the caller with \"pong\"")]
|
||||
// The `CommandHelper` attribute can be used to provide additional information about the command.
|
||||
[CommandHelper(minArgs: 1, usage: "[name]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
[RequiresPermissions("@css/cvar")]
|
||||
public void OnHelloCommand(CCSPlayerController? player, CommandInfo commandInfo)
|
||||
{
|
||||
// The first argument is the command name, in this case "css_hello".
|
||||
commandInfo.GetArg(0); // css_hello
|
||||
|
||||
// The second argument is the first argument passed to the command, in this case "name".
|
||||
// The `minArgs` helper parameter is used to ensure that the second argument is present.
|
||||
var name = commandInfo.GetArg(1);
|
||||
|
||||
commandInfo.ReplyToCommand($"Hello {name}");
|
||||
}
|
||||
|
||||
// Permissions can be added to commands using the `RequiresPermissions` attribute.
|
||||
// See the admin documentation for more information on permissions.
|
||||
[RequiresPermissions("@css/kick")]
|
||||
[CommandHelper(minArgs: 1, usage: "[id]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
public void OnSpecialCommand(CCSPlayerController? player, CommandInfo commandInfo)
|
||||
{
|
||||
var id = commandInfo.GetArg(1);
|
||||
|
||||
Server.ExecuteCommand($"kick {id}");
|
||||
}
|
||||
}
|
||||
2
examples/WithConfig/README.md
Normal file
2
examples/WithConfig/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# WithConfig
|
||||
This example shows how you can implement the `IPluginConfig` interface to allow for semi-automatic config parsing & loading.
|
||||
12
examples/WithConfig/WithConfig.csproj
Normal file
12
examples/WithConfig/WithConfig.csproj
Normal file
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
38
examples/WithConfig/WithConfigPlugin.cs
Normal file
38
examples/WithConfig/WithConfigPlugin.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
|
||||
namespace WithConfig;
|
||||
|
||||
public class SampleConfig : BasePluginConfig
|
||||
{
|
||||
[JsonPropertyName("ChatPrefix")] public string ChatPrefix { get; set; } = "My Cool Plugin";
|
||||
|
||||
[JsonPropertyName("ChatInterval")] public float ChatInterval { get; set; } = 60;
|
||||
}
|
||||
|
||||
[MinimumApiVersion(80)]
|
||||
public class WithConfigPlugin : BasePlugin, IPluginConfig<SampleConfig>
|
||||
{
|
||||
public override string ModuleName => "Example: With Config";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
|
||||
public SampleConfig Config { get; set; }
|
||||
|
||||
public void OnConfigParsed(SampleConfig config)
|
||||
{
|
||||
// Do manual verification of the config and override any invalid values
|
||||
if (config.ChatInterval > 60)
|
||||
{
|
||||
config.ChatInterval = 60;
|
||||
}
|
||||
|
||||
if (config.ChatPrefix.Length > 25)
|
||||
{
|
||||
throw new Exception($"Invalid value has been set to config value 'ChatPrefix': {config.ChatPrefix}");
|
||||
}
|
||||
|
||||
// Once we've validated the config, we can set it to the instance
|
||||
Config = config;
|
||||
}
|
||||
}
|
||||
2
examples/WithDatabaseDapper/README.md
Normal file
2
examples/WithDatabaseDapper/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# With Database (Dapper)
|
||||
Simple SQLite database example using Dapper library to track kills.
|
||||
16
examples/WithDatabaseDapper/WithDatabaseDapper.csproj
Normal file
16
examples/WithDatabaseDapper/WithDatabaseDapper.csproj
Normal file
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.24" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.14" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
89
examples/WithDatabaseDapper/WithDatabaseDapperPlugin.cs
Normal file
89
examples/WithDatabaseDapper/WithDatabaseDapperPlugin.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using Dapper;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace WithDatabaseDapper;
|
||||
|
||||
[MinimumApiVersion(80)]
|
||||
public class WithDatabaseDapperPlugin : BasePlugin
|
||||
{
|
||||
public override string ModuleName => "Example: With Database (Dapper)";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
|
||||
public override string ModuleDescription => "A plugin that reads and writes from the database.";
|
||||
|
||||
private SqliteConnection _connection = null!;
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
Logger.LogInformation("Loading database from {Path}", Path.Join(ModuleDirectory, "database.db"));
|
||||
_connection = new SqliteConnection($"Data Source={Path.Join(ModuleDirectory, "database.db")}");
|
||||
_connection.Open();
|
||||
|
||||
// Create the table if it doesn't exist
|
||||
// Run in a separate thread to avoid blocking the main thread
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await _connection.ExecuteAsync(@"
|
||||
CREATE TABLE IF NOT EXISTS `players` (
|
||||
`steamid` UNSIGNED BIG INT NOT NULL,
|
||||
`kills` INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`steamid`));");
|
||||
});
|
||||
}
|
||||
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerKilled(EventPlayerDeath @event, GameEventInfo info)
|
||||
{
|
||||
// Don't count suicides.
|
||||
if (@event.Attacker == @event.Userid) return HookResult.Continue;
|
||||
|
||||
// Capture the steamid of the player as `@event` will not be available outside of this function.
|
||||
var steamId = @event.Attacker.AuthorizedSteamID?.SteamId64;
|
||||
|
||||
if (steamId == null) return HookResult.Continue;
|
||||
|
||||
// Run in a separate thread to avoid blocking the main thread
|
||||
Task.Run(async () =>
|
||||
{
|
||||
// insert or update the player's kills
|
||||
await _connection.ExecuteAsync(@"
|
||||
INSERT INTO `players` (`steamid`, `kills`) VALUES (@SteamId, 1)
|
||||
ON CONFLICT(`steamid`) DO UPDATE SET `kills` = `kills` + 1;",
|
||||
new
|
||||
{
|
||||
SteamId = steamId
|
||||
});
|
||||
});
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
[ConsoleCommand("css_kills", "Get count of kills for a player")]
|
||||
public void OnKillsCommand(CCSPlayerController? player, CommandInfo commandInfo)
|
||||
{
|
||||
if (player == null) return;
|
||||
|
||||
// Capture the SteamID of the player as `@event` will not be available outside of this function.
|
||||
var steamId = player.AuthorizedSteamID.SteamId64;
|
||||
|
||||
// Run in a separate thread to avoid blocking the main thread
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var result = await _connection.QueryFirstOrDefaultAsync(@"SELECT `kills` FROM `players` WHERE `steamid` = @SteamId;",
|
||||
new
|
||||
{
|
||||
SteamId = steamId
|
||||
});
|
||||
|
||||
// Print the result to the player's chat. Note that this needs to be run on the game thread.
|
||||
// So we use `Server.NextFrame` to run it on the next game tick.
|
||||
Server.NextFrame(() => { player.PrintToChat($"Kills: {result?.kills ?? 0}"); });
|
||||
});
|
||||
}
|
||||
}
|
||||
2
examples/WithDependencyInjection/README.md
Normal file
2
examples/WithDependencyInjection/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# With Dependency Injection
|
||||
This example shows how can you implement the `IPluginServiceCollection` interface to register your services in a provided DI container.
|
||||
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,50 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace WithDependencyInjection;
|
||||
|
||||
[MinimumApiVersion(80)]
|
||||
public class WithDependencyInjectionPlugin : BasePlugin
|
||||
{
|
||||
public override string ModuleName => "Example: Dependency Injection";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
|
||||
public override string ModuleDescription => "An example plugin that uses dependency injection.";
|
||||
|
||||
private readonly TestInjectedClass _testInjectedClass;
|
||||
|
||||
public WithDependencyInjectionPlugin(TestInjectedClass testInjectedClass)
|
||||
{
|
||||
_testInjectedClass = testInjectedClass;
|
||||
}
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
_testInjectedClass.SayHello();
|
||||
}
|
||||
}
|
||||
|
||||
public class WithDependencyInjectionPluginServiceCollection : IPluginServiceCollection<WithDependencyInjectionPlugin>
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddSingleton<TestInjectedClass>();
|
||||
}
|
||||
}
|
||||
|
||||
public class TestInjectedClass
|
||||
{
|
||||
private readonly ILogger<TestInjectedClass> _logger;
|
||||
|
||||
public TestInjectedClass(ILogger<TestInjectedClass> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void SayHello()
|
||||
{
|
||||
_logger.LogInformation("Hello World from Test Injected Class");
|
||||
}
|
||||
}
|
||||
2
examples/WithGameEventHandlers/README.md
Normal file
2
examples/WithGameEventHandlers/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# With Game Event Handlers
|
||||
This is example shows how you can subscribe to legacy Source 1 game events.
|
||||
12
examples/WithGameEventHandlers/WithGameEventHandlers.csproj
Normal file
12
examples/WithGameEventHandlers/WithGameEventHandlers.csproj
Normal file
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,54 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace WithGameEventHandlers;
|
||||
|
||||
[MinimumApiVersion(80)]
|
||||
public class WithGameEventHandlersPlugin : BasePlugin
|
||||
{
|
||||
public override string ModuleName => "Example: With Game Event Handlers";
|
||||
public override string ModuleVersion => "1.0.0";
|
||||
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
|
||||
public override string ModuleDescription => "A simple plugin that subscribes to game events";
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
// Subscriptions can be added via the instance method
|
||||
RegisterEventHandler<EventPlayerDeath>((@event, info) =>
|
||||
{
|
||||
// You can use `info.DontBroadcast` to set the dont broadcast flag on the event (in pre handlers)
|
||||
// This will prevent the event from being broadcast to other clients.
|
||||
// In this example we prevent kill-feed messages from being broadcast if it was not a headshot.
|
||||
if (!@event.Headshot)
|
||||
{
|
||||
@event.Attacker.PrintToChat($"Skipping player_death broadcast");
|
||||
info.DontBroadcast = true;
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}, HookMode.Pre);
|
||||
}
|
||||
|
||||
// Subscriptions can be added via an attribute
|
||||
[GameEventHandler]
|
||||
public HookResult OnPlayerBlind(EventPlayerBlind @event, GameEventInfo info)
|
||||
{
|
||||
Logger.LogInformation("Player was just blinded for {Duration}", @event.BlindDuration);
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// The event name is inferred from the event type you pass to the first argument.
|
||||
// e.g. EventRoundStart becomes "round_start"
|
||||
// Note: You can use the `HookMode` enum to specify the hook mode
|
||||
// If you do not specify a hook mode, it will default to `HookMode.Post`
|
||||
[GameEventHandler(HookMode.Pre)]
|
||||
public HookResult OnEventRoundStartPre(EventRoundStart @event, GameEventInfo info)
|
||||
{
|
||||
Logger.LogInformation("Round has started with Timelimit: {Timelimit}", @event.Timelimit);
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
1
libraries/DynoHook
Submodule
1
libraries/DynoHook
Submodule
Submodule libraries/DynoHook added at d7f8ebb059
1
libraries/asmjit
Submodule
1
libraries/asmjit
Submodule
Submodule libraries/asmjit added at 0dd16b0a98
Submodule libraries/hl2sdk-cs2 updated: 492b8f9663...1d394d3365
@@ -1,24 +1,20 @@
|
||||
include("makefiles/shared.cmake")
|
||||
|
||||
#SET(_ITERATOR_DEBUG_LEVEL 2)
|
||||
#set(_ITERATOR_DEBUG_LEVEL 2)
|
||||
add_definitions(-D_LINUX -DPOSIX -DLINUX -DGNUC -DCOMPILER_GCC -DPLATFORM_64BITS)
|
||||
#Add_Definitions(-DCOMPILER_MSVC -DCOMPILER_MSVC32 -D_WIN32 -D_WINDOWS -D_ALLOW_KEYWORD_MACROS -D__STDC_LIMIT_MACROS)
|
||||
|
||||
# Set(CMAKE_CXX_FLAGS_RELEASE "/D_NDEBUG /MD /wd4005 /MP")
|
||||
|
||||
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp")
|
||||
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dstrnicmp=strncasecmp -D_snprintf=snprintf")
|
||||
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dstrnicmp=strncasecmp -D_snprintf=snprintf")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp")
|
||||
|
||||
# Warnings
|
||||
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-uninitialized -Wno-switch -Wno-unused")
|
||||
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-non-virtual-dtor -Wno-overloaded-virtual")
|
||||
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-conversion-null -Wno-write-strings")
|
||||
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -Wno-reorder")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-uninitialized -Wno-switch -Wno-unused")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-non-virtual-dtor -Wno-overloaded-virtual")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-conversion-null -Wno-write-strings")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -Wno-reorder")
|
||||
|
||||
# Others
|
||||
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse -msse -fno-strict-aliasing")
|
||||
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fno-threadsafe-statics -v -fvisibility=default")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse -msse -fno-strict-aliasing")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-threadsafe-statics -v -fvisibility=default")
|
||||
|
||||
SET(
|
||||
COUNTER_STRIKE_SHARP_LINK_LIBRARIES
|
||||
@@ -31,4 +27,5 @@ SET(
|
||||
dyncall_s
|
||||
distorm
|
||||
funchook-static
|
||||
dynohook
|
||||
)
|
||||
10
makefiles/metamod/configure_metamod.cmake
Normal file
10
makefiles/metamod/configure_metamod.cmake
Normal file
@@ -0,0 +1,10 @@
|
||||
if (WIN32)
|
||||
set(COUNTERSTRIKESHARP_VDF_PLATFORM "win64")
|
||||
else()
|
||||
set(COUNTERSTRIKESHARP_VDF_PLATFORM "linuxsteamrt64")
|
||||
endif()
|
||||
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/counterstrikesharp.vdf.in
|
||||
${PROJECT_SOURCE_DIR}/configs/addons/metamod/counterstrikesharp.vdf
|
||||
)
|
||||
5
makefiles/metamod/counterstrikesharp.vdf.in
Normal file
5
makefiles/metamod/counterstrikesharp.vdf.in
Normal file
@@ -0,0 +1,5 @@
|
||||
"Metamod Plugin"
|
||||
{
|
||||
"alias" "counterstrikesharp"
|
||||
"file" "addons/counterstrikesharp/bin/${COUNTERSTRIKESHARP_VDF_PLATFORM}/counterstrikesharp"
|
||||
}
|
||||
@@ -1,40 +1,58 @@
|
||||
Set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING
|
||||
"Only do Release and Debug"
|
||||
FORCE
|
||||
if (UNIX AND NOT APPLE)
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
|
||||
if (WIN32 AND NOT MSVC)
|
||||
message(FATAL "MSVC restricted.")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING
|
||||
"Only do Release and Debug"
|
||||
FORCE
|
||||
)
|
||||
|
||||
# TODO: Use C++20 instead.
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
||||
Set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
|
||||
set(CMAKE_STATIC_LIBRARY_PREFIX "")
|
||||
|
||||
Set(SOURCESDK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2)
|
||||
Set(METAMOD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/metamod-source)
|
||||
set(SOURCESDK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2)
|
||||
set(METAMOD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/metamod-source)
|
||||
|
||||
Set(SOURCESDK ${SOURCESDK_DIR}/${BRANCH})
|
||||
Set(SOURCESDK_LIB ${SOURCESDK}/lib)
|
||||
set(SOURCESDK ${SOURCESDK_DIR}/${BRANCH})
|
||||
set(SOURCESDK_LIB ${SOURCESDK}/lib)
|
||||
|
||||
add_definitions(-DMETA_IS_SOURCE2)
|
||||
|
||||
include_directories(
|
||||
${SOURCESDK}
|
||||
${SOURCESDK}/common
|
||||
${SOURCESDK}/game/shared
|
||||
${SOURCESDK}/game/server
|
||||
${SOURCESDK}/public
|
||||
${SOURCESDK}/public/engine
|
||||
${SOURCESDK}/public/mathlib
|
||||
${SOURCESDK}/public/tier0
|
||||
${SOURCESDK}/public/tier1
|
||||
${SOURCESDK}/public/entity2
|
||||
${SOURCESDK}/public/game/server
|
||||
${SOURCESDK}/public/entity2
|
||||
${METAMOD_DIR}/core
|
||||
${METAMOD_DIR}/core/sourcehook
|
||||
libraries/dyncall/dynload
|
||||
libraries/dyncall/dyncall
|
||||
libraries/spdlog/include
|
||||
libraries/tl
|
||||
libraries/funchook/include
|
||||
libraries
|
||||
${SOURCESDK}
|
||||
${SOURCESDK}/thirdparty/protobuf-3.21.8/src
|
||||
${SOURCESDK}/common
|
||||
${SOURCESDK}/game/shared
|
||||
${SOURCESDK}/game/server
|
||||
${SOURCESDK}/public
|
||||
${SOURCESDK}/public/engine
|
||||
${SOURCESDK}/public/mathlib
|
||||
${SOURCESDK}/public/tier0
|
||||
${SOURCESDK}/public/tier1
|
||||
${SOURCESDK}/public/entity2
|
||||
${SOURCESDK}/public/game/server
|
||||
${SOURCESDK}/public/entity2
|
||||
${METAMOD_DIR}/core
|
||||
${METAMOD_DIR}/core/sourcehook
|
||||
libraries/dyncall/dynload
|
||||
libraries/dyncall/dyncall
|
||||
libraries/spdlog/include
|
||||
libraries/tl
|
||||
libraries/funchook/include
|
||||
libraries/DynoHook/src
|
||||
libraries
|
||||
)
|
||||
|
||||
Project(counterstrikesharp C CXX)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/metamod/configure_metamod.cmake)
|
||||
|
||||
if (LINUX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
||||
endif()
|
||||
21
makefiles/windows.base.cmake
Normal file
21
makefiles/windows.base.cmake
Normal file
@@ -0,0 +1,21 @@
|
||||
add_definitions(
|
||||
-DCOMPILER_MSVC -DCOMPILER_MSVC64 -D_WIN32 -D_WINDOWS -D_ALLOW_KEYWORD_MACROS -D__STDC_LIMIT_MACROS
|
||||
-D_CRT_SECURE_NO_WARNINGS=1 -D_CRT_SECURE_NO_DEPRECATE=1 -D_CRT_NONSTDC_NO_DEPRECATE=1
|
||||
)
|
||||
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819 /wd4828 /wd5033 /permissive- /utf-8 /wd4005 /MP")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /OPT:REF /OPT:ICF")
|
||||
|
||||
set(COUNTER_STRIKE_SHARP_LINK_LIBRARIES
|
||||
${SOURCESDK_LIB}/public/win64/tier0.lib
|
||||
${SOURCESDK_LIB}/public/win64/tier1.lib
|
||||
${SOURCESDK_LIB}/public/win64/interfaces.lib
|
||||
${SOURCESDK_LIB}/public/win64/mathlib.lib
|
||||
spdlog
|
||||
dynload_s
|
||||
dyncall_s
|
||||
distorm
|
||||
funchook-static
|
||||
dynohook
|
||||
)
|
||||
@@ -30,5 +30,19 @@ namespace CounterStrikeSharp.API
|
||||
{
|
||||
Handle = pointer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of the specified type using the pointer from the passed in object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Useful for creating a new instance of a class that inherits from NativeObject.
|
||||
/// e.g. <code>var weaponServices = playerWeaponServices.As<CCSPlayer_WeaponServices>();</code>
|
||||
/// </remarks>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public T As<T>() where T : NativeObject
|
||||
{
|
||||
return (T)Activator.CreateInstance(typeof(T), this.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
managed/CounterStrikeSharp.API/Bootstrap.cs
Normal file
67
managed/CounterStrikeSharp.API/Bootstrap.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using CounterStrikeSharp.API.Core.Logging;
|
||||
using CounterStrikeSharp.API.Core.Plugin;
|
||||
using CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
|
||||
namespace CounterStrikeSharp.API;
|
||||
|
||||
public static class Bootstrap
|
||||
{
|
||||
[UnmanagedCallersOnly]
|
||||
// Used by .NET Host in C++ to initiate loading
|
||||
public static int Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Path to /game/csgo/addons/counterstrikesharp
|
||||
var contentRoot = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent.FullName;
|
||||
|
||||
using var host = Host.CreateDefaultBuilder()
|
||||
.UseContentRoot(contentRoot)
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddLogging(builder =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
builder.AddCoreLogging(contentRoot);
|
||||
});
|
||||
|
||||
services.AddSingleton<IScriptHostConfiguration, ScriptHostConfiguration>();
|
||||
services.AddScoped<Application>();
|
||||
services.AddSingleton<IPluginManager, PluginManager>();
|
||||
services.AddScoped<IPluginContextQueryHandler, PluginContextQueryHandler>();
|
||||
|
||||
services.Scan(i => i.FromCallingAssembly()
|
||||
.AddClasses(c => c.AssignableTo<IStartupService>())
|
||||
.AsSelfWithInterfaces()
|
||||
.WithSingletonLifetime());
|
||||
})
|
||||
.Build();
|
||||
|
||||
using IServiceScope scope = host.Services.CreateScope();
|
||||
|
||||
// TODO: Improve static singleton access
|
||||
GameData.GameDataProvider = scope.ServiceProvider.GetRequiredService<GameDataProvider>();
|
||||
|
||||
var application = scope.ServiceProvider.GetRequiredService<Application>();
|
||||
application.Start();
|
||||
|
||||
return 1;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.Error.WriteLine(e);
|
||||
Log.Fatal(e, "Failed to start application");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr AddCommand(string name, string description, bool serveronly, int flags, InputArgument callback){
|
||||
public static void AddCommand(string name, string description, bool serveronly, int flags, InputArgument callback){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(name);
|
||||
@@ -41,7 +41,6 @@ namespace CounterStrikeSharp.API.Core
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x807C6B9C);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (IntPtr)ScriptContext.GlobalScriptContext.GetResult(typeof(IntPtr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +157,80 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetClientConvarValue(int clientindex, string convarname){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(clientindex);
|
||||
ScriptContext.GlobalScriptContext.Push(convarname);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xAE4B1B79);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (string)ScriptContext.GlobalScriptContext.GetResult(typeof(string));
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetFakeClientConvarValue(int clientindex, string convarname, string convarvalue){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(clientindex);
|
||||
ScriptContext.GlobalScriptContext.Push(convarname);
|
||||
ScriptContext.GlobalScriptContext.Push(convarvalue);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x4C61E8BB);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static T DynamicHookGetReturn<T>(IntPtr hook, int datatype){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(hook);
|
||||
ScriptContext.GlobalScriptContext.Push(datatype);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x4F5B80D0);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (T)ScriptContext.GlobalScriptContext.GetResult(typeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
public static void DynamicHookSetReturn<T>(IntPtr hook, int datatype, T value){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(hook);
|
||||
ScriptContext.GlobalScriptContext.Push(datatype);
|
||||
ScriptContext.GlobalScriptContext.Push(value);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xDB297E44);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static T DynamicHookGetParam<T>(IntPtr hook, int datatype, int paramindex){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(hook);
|
||||
ScriptContext.GlobalScriptContext.Push(datatype);
|
||||
ScriptContext.GlobalScriptContext.Push(paramindex);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x5F5ABDD5);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (T)ScriptContext.GlobalScriptContext.GetResult(typeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
public static void DynamicHookSetParam<T>(IntPtr hook, int datatype, int paramindex, T value){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(hook);
|
||||
ScriptContext.GlobalScriptContext.Push(datatype);
|
||||
ScriptContext.GlobalScriptContext.Push(paramindex);
|
||||
ScriptContext.GlobalScriptContext.Push(value);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xA96CFBC1);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetMapName(){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -406,6 +479,16 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static void QueueTaskForNextWorldUpdate(IntPtr callback){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(callback);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xAD51A0C9);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr GetValveInterface(int interfacetype, string interfacename){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -485,6 +568,49 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static uint GetRefFromEntityPointer(IntPtr entitypointer){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(entitypointer);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xAF13DA94);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (uint)ScriptContext.GlobalScriptContext.GetResult(typeof(uint));
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr GetEntityPointerFromRef(uint entityref){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(entityref);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xDBC17174);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (IntPtr)ScriptContext.GlobalScriptContext.GetResult(typeof(IntPtr));
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr GetConcreteEntityListPointer(){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x5756DB36);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (IntPtr)ScriptContext.GlobalScriptContext.GetResult(typeof(IntPtr));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsRefValidEntity(uint entityref){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(entityref);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x6E38A1FC);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (bool)ScriptContext.GlobalScriptContext.GetResult(typeof(bool));
|
||||
}
|
||||
}
|
||||
|
||||
public static void PrintToConsole(int index, string message){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -506,6 +632,28 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static ulong GetPlayerAuthorizedSteamid(int slot){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(slot);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xD1F30B3B);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (ulong)ScriptContext.GlobalScriptContext.GetResult(typeof(ulong));
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetPlayerIpAddress(int slot){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(slot);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x46A45CB0);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (string)ScriptContext.GlobalScriptContext.GetResult(typeof(string));
|
||||
}
|
||||
}
|
||||
|
||||
public static void HookEvent(string name, InputArgument callback, bool ispost){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -553,6 +701,17 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static void FireEventToClient(IntPtr gameevent, int clientindex){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(gameevent);
|
||||
ScriptContext.GlobalScriptContext.Push(clientindex);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x40B7C06C);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetEventName(IntPtr gameevent){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -793,6 +952,30 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static void HookFunction(IntPtr function, InputArgument hook, bool post){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(function);
|
||||
ScriptContext.GlobalScriptContext.Push((InputArgument)hook);
|
||||
ScriptContext.GlobalScriptContext.Push(post);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xA6C8BA9B);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnhookFunction(IntPtr function, InputArgument hook, bool post){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(function);
|
||||
ScriptContext.GlobalScriptContext.Push((InputArgument)hook);
|
||||
ScriptContext.GlobalScriptContext.Push(post);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x2051B00);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static T ExecuteVirtualFunction<T>(IntPtr function, object[] arguments){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -883,6 +1066,17 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetSchemaClassSize(string classname){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(classname);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x9CE4FC56);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (int)ScriptContext.GlobalScriptContext.GetResult(typeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr GetEconItemSystem(){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -893,6 +1087,16 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsServerPaused(){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xB216AAAC);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (bool)ScriptContext.GlobalScriptContext.GetResult(typeof(bool));
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr CreateTimer(float interval, InputArgument callback, int flags){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
|
||||
254
managed/CounterStrikeSharp.API/Core/Application.cs
Normal file
254
managed/CounterStrikeSharp.API/Core/Application.cs
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using CounterStrikeSharp.API.Core.Plugin;
|
||||
using CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Menu;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
public sealed class Application
|
||||
{
|
||||
private static Application _instance = null!;
|
||||
public ILogger Logger { get; }
|
||||
public static Application Instance => _instance!;
|
||||
|
||||
public static string RootDirectory => Instance._scriptHostConfiguration.RootPath;
|
||||
|
||||
private readonly IScriptHostConfiguration _scriptHostConfiguration;
|
||||
private readonly GameDataProvider _gameDataProvider;
|
||||
private readonly CoreConfig _coreConfig;
|
||||
private readonly IPluginManager _pluginManager;
|
||||
private readonly IPluginContextQueryHandler _pluginContextQueryHandler;
|
||||
|
||||
public Application(ILoggerFactory loggerFactory, IScriptHostConfiguration scriptHostConfiguration,
|
||||
GameDataProvider gameDataProvider, CoreConfig coreConfig, IPluginManager pluginManager,
|
||||
IPluginContextQueryHandler pluginContextQueryHandler)
|
||||
{
|
||||
Logger = loggerFactory.CreateLogger("Core");
|
||||
_scriptHostConfiguration = scriptHostConfiguration;
|
||||
_gameDataProvider = gameDataProvider;
|
||||
_coreConfig = coreConfig;
|
||||
_pluginManager = pluginManager;
|
||||
_pluginContextQueryHandler = pluginContextQueryHandler;
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Logger.LogInformation("CounterStrikeSharp is starting up...");
|
||||
|
||||
_coreConfig.Load();
|
||||
_gameDataProvider.Load();
|
||||
|
||||
var adminGroupsPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_groups.json");
|
||||
Logger.LogInformation("Loading Admin Groups from {Path}", adminGroupsPath);
|
||||
AdminManager.LoadAdminGroups(adminGroupsPath);
|
||||
|
||||
var adminPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admins.json");
|
||||
Logger.LogInformation("Loading Admins from {Path}", adminPath);
|
||||
AdminManager.LoadAdminData(adminPath);
|
||||
|
||||
var overridePath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_overrides.json");
|
||||
Logger.LogInformation("Loading Admin Command Overrides from {Path}", overridePath);
|
||||
AdminManager.LoadCommandOverrides(overridePath);
|
||||
|
||||
AdminManager.MergeGroupPermsIntoAdmins();
|
||||
|
||||
_pluginManager.Load();
|
||||
|
||||
for (var i = 1; i <= 9; i++)
|
||||
{
|
||||
CommandUtils.AddStandaloneCommand("css_" + i, "Command Key Handler", (player, info) =>
|
||||
{
|
||||
if (player == null) return;
|
||||
var key = Convert.ToInt32(info.GetArg(0).Split("_")[1]);
|
||||
ChatMenus.OnKeyPress(player, key);
|
||||
});
|
||||
}
|
||||
|
||||
RegisterPluginCommands();
|
||||
}
|
||||
|
||||
[RequiresPermissions("@css/generic")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
private void OnCSSCommand(CCSPlayerController? caller, CommandInfo info)
|
||||
{
|
||||
var currentVersion = Api.GetVersion();
|
||||
|
||||
info.ReplyToCommand(
|
||||
" CounterStrikeSharp was created and is maintained by Michael \"roflmuffin\" Wilson.\n" +
|
||||
" Counter-Strike Sharp uses code borrowed from SourceMod, Source.Python, FiveM, Saul Rennison and CS2Fixes.\n" +
|
||||
" See ACKNOWLEDGEMENTS.md for more information.\n" +
|
||||
" Current API Version: " + currentVersion, true);
|
||||
return;
|
||||
}
|
||||
|
||||
[RequiresPermissions("@css/generic")]
|
||||
[CommandHelper(minArgs: 1,
|
||||
usage: "[option]\n" +
|
||||
" list - List all plugins currently loaded.\n" +
|
||||
" start / load - Loads a plugin not currently loaded.\n" +
|
||||
" stop / unload - Unloads a plugin currently loaded.\n" +
|
||||
" restart / reload - Reloads a plugin currently loaded.",
|
||||
whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
private void OnCSSPluginCommand(CCSPlayerController? caller, CommandInfo info)
|
||||
{
|
||||
switch (info.GetArg(1))
|
||||
{
|
||||
case "list":
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
$" List of all plugins currently loaded by CounterStrikeSharp: {_pluginManager.GetLoadedPlugins().Count()} plugins loaded.",
|
||||
true);
|
||||
|
||||
foreach (var plugin in _pluginManager.GetLoadedPlugins())
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendFormat(" [#{0}:{1}]: \"{2}\" ({3})", plugin.PluginId,
|
||||
plugin.State.ToString().ToUpper(), plugin.Plugin.ModuleName,
|
||||
plugin.Plugin.ModuleVersion);
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin.ModuleAuthor))
|
||||
sb.AppendFormat(" by {0}", plugin.Plugin.ModuleAuthor);
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin.ModuleDescription))
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.Append(" ");
|
||||
sb.Append(plugin.Plugin.ModuleDescription);
|
||||
}
|
||||
|
||||
info.ReplyToCommand(sb.ToString(), true);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "start":
|
||||
case "load":
|
||||
{
|
||||
if (info.ArgCount < 2)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n",
|
||||
true);
|
||||
break;
|
||||
}
|
||||
|
||||
// If our arugment doesn't end in ".dll" - try and construct a path similar to PluginName/PluginName.dll.
|
||||
// We'll assume we have a full path if we have ".dll".
|
||||
var path = info.GetArg(2);
|
||||
if (!path.EndsWith(".dll"))
|
||||
{
|
||||
path = Path.Combine(_scriptHostConfiguration.RootPath, $"plugins/{path}/{path}.dll");
|
||||
}
|
||||
else
|
||||
{
|
||||
path = Path.Combine(_scriptHostConfiguration.RootPath, path);
|
||||
}
|
||||
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_pluginManager.LoadPlugin(path);
|
||||
} catch (Exception e)
|
||||
{
|
||||
info.ReplyToCommand($"Could not load plugin \"{path}\")", true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
plugin.Load(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "stop":
|
||||
case "unload":
|
||||
{
|
||||
if (info.ArgCount < 2)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n",
|
||||
true);
|
||||
break;
|
||||
}
|
||||
|
||||
var pluginIdentifier = info.GetArg(2);
|
||||
IPluginContext? plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier);
|
||||
if (plugin == null)
|
||||
{
|
||||
info.ReplyToCommand($"Could not unload plugin \"{pluginIdentifier}\")", true);
|
||||
break;
|
||||
}
|
||||
|
||||
plugin.Unload(false);
|
||||
break;
|
||||
}
|
||||
|
||||
case "restart":
|
||||
case "reload":
|
||||
{
|
||||
if (info.ArgCount < 2)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n",
|
||||
true);
|
||||
break;
|
||||
}
|
||||
|
||||
var pluginIdentifier = info.GetArg(2);
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
info.ReplyToCommand($"Could not reload plugin \"{pluginIdentifier}\")", true);
|
||||
break;
|
||||
}
|
||||
|
||||
plugin.Unload(true);
|
||||
plugin.Load(true);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
info.ReplyToCommand("Valid usage: css_plugins [option]\n" +
|
||||
" list - List all plugins currently loaded.\n" +
|
||||
" start / load - Loads a plugin not currently loaded.\n" +
|
||||
" stop / unload - Unloads a plugin currently loaded.\n" +
|
||||
" restart / reload - Reloads a plugin currently loaded."
|
||||
, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterPluginCommands()
|
||||
{
|
||||
CommandUtils.AddStandaloneCommand("css", "Counter-Strike Sharp options.", OnCSSCommand);
|
||||
CommandUtils.AddStandaloneCommand("css_plugins", "Counter-Strike Sharp plugin options.",
|
||||
OnCSSPluginCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,22 +18,19 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Loader;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Events;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Listeners;
|
||||
using CounterStrikeSharp.API.Modules.Timers;
|
||||
using CounterStrikeSharp.API.Modules.Config;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
public abstract class BasePlugin : IPlugin, IDisposable
|
||||
public abstract class BasePlugin : IPlugin
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
@@ -48,9 +45,10 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
public virtual string ModuleDescription { get; }
|
||||
|
||||
public string ModulePath { get; internal set; }
|
||||
public string ModulePath { get; set; }
|
||||
|
||||
public string ModuleDirectory => Path.GetDirectoryName(ModulePath);
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public virtual void Load(bool hotReload)
|
||||
{
|
||||
@@ -160,6 +158,42 @@ namespace CounterStrikeSharp.API.Core
|
||||
var command = new CommandInfo(ptr, caller);
|
||||
|
||||
var methodInfo = handler?.GetMethodInfo();
|
||||
|
||||
if (!AdminManager.CommandIsOverriden(name))
|
||||
{
|
||||
// Do not execute command if we do not have the correct permissions.
|
||||
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
|
||||
if (permissions != null)
|
||||
{
|
||||
foreach (var attr in permissions)
|
||||
{
|
||||
attr.Command = name;
|
||||
if (!attr.CanExecuteCommand(caller))
|
||||
{
|
||||
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If this command has it's permissions overriden, we will do an AND check for all permissions.
|
||||
else
|
||||
{
|
||||
// I don't know if this is the most sane implementation of this, can be edited in code review.
|
||||
var data = AdminManager.GetCommandOverrideData(name);
|
||||
if (data != null)
|
||||
{
|
||||
var attrType = (data.CheckType == "all") ? typeof(RequiresPermissions) : typeof(RequiresPermissionsOr);
|
||||
var attr = (BaseRequiresPermissions)Activator.CreateInstance(attrType, args: AdminManager.GetPermissionOverrides(name));
|
||||
attr.Command = name;
|
||||
if (!attr.CanExecuteCommand(caller))
|
||||
{
|
||||
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do not execute if we shouldn't be calling this command.
|
||||
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
|
||||
if (helperAttribute != null)
|
||||
@@ -183,14 +217,6 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
// Do not execute command if we do not have the correct permissions.
|
||||
var permissions = methodInfo?.GetCustomAttribute<RequiresPermissions>()?.RequiredPermissions;
|
||||
if (permissions != null && !AdminManager.PlayerHasPermissions(caller, permissions))
|
||||
{
|
||||
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
handler?.Invoke(caller, command);
|
||||
});
|
||||
|
||||
@@ -207,14 +233,10 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
var wrappedHandler = new Func<int, IntPtr, HookResult>((i, ptr) =>
|
||||
{
|
||||
if (i == -1)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
var caller = (i != -1) ? new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1)) : null;
|
||||
|
||||
var entity = new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1));
|
||||
var command = new CommandInfo(ptr, entity);
|
||||
return handler.Invoke(entity.IsValid ? entity : null, command);
|
||||
var command = new CommandInfo(ptr, caller);
|
||||
return handler.Invoke(caller, command);
|
||||
});
|
||||
|
||||
var subscriber = new CallbackSubscriber(handler, wrappedHandler, () => { RemoveCommandListener(name, handler, mode); });
|
||||
@@ -288,7 +310,8 @@ namespace CounterStrikeSharp.API.Core
|
||||
.Select(p => p.GetCustomAttribute<CastFromAttribute>()?.Type)
|
||||
.ToArray();
|
||||
|
||||
Console.WriteLine($"Registering listener for {listenerName} with {parameterTypes.Length}");
|
||||
Application.Instance.Logger.LogDebug("Registering listener for {ListenerName} with {ParameterCount} parameters",
|
||||
listenerName, parameterTypes.Length);
|
||||
|
||||
var wrappedHandler = new Action<ScriptContext>(context =>
|
||||
{
|
||||
@@ -333,6 +356,31 @@ namespace CounterStrikeSharp.API.Core
|
||||
this.RegisterConsoleCommandAttributeHandlers(instance);
|
||||
}
|
||||
|
||||
public void InitializeConfig(object instance, Type pluginType)
|
||||
{
|
||||
Type[] interfaces = pluginType.GetInterfaces();
|
||||
Func<Type, bool> predicate = (i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPluginConfig<>));
|
||||
|
||||
// if the plugin has set a configuration type (implements IPluginConfig<>)
|
||||
if (interfaces.Any(predicate))
|
||||
{
|
||||
// IPluginConfig<>
|
||||
Type @interface = interfaces.Where(predicate).FirstOrDefault()!;
|
||||
|
||||
// custom config type passed as generic
|
||||
Type genericType = @interface!.GetGenericArguments().First();
|
||||
|
||||
var config = typeof(ConfigManager)
|
||||
.GetMethod("Load")!
|
||||
.MakeGenericMethod(genericType)
|
||||
.Invoke(null, new object[] { Path.GetFileName(ModuleDirectory) }) as IBasePluginConfig;
|
||||
|
||||
// we KNOW that we can do this "safely"
|
||||
pluginType.GetRuntimeMethod("OnConfigParsed", new Type[] { genericType })
|
||||
.Invoke(instance, new object[] { config });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers all game event handlers that are decorated with the `[GameEventHandler]` attribute.
|
||||
/// </summary>
|
||||
|
||||
32
managed/CounterStrikeSharp.API/Core/BasePluginConfig.cs
Normal file
32
managed/CounterStrikeSharp.API/Core/BasePluginConfig.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
public interface IBasePluginConfig
|
||||
{
|
||||
[JsonPropertyName("ConfigVersion")]
|
||||
int Version { get; set; }
|
||||
}
|
||||
|
||||
public class BasePluginConfig : IBasePluginConfig
|
||||
{
|
||||
[JsonPropertyName("ConfigVersion")]
|
||||
public virtual int Version { get; set; } = 1;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,37 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public const string ROOT_BINARY_PATH = "/bin/linuxsteamrt64/";
|
||||
public const string GAME_BINARY_PATH = "/csgo/bin/linuxsteamrt64/";
|
||||
public static string ModulePrefix { get; }
|
||||
|
||||
public static string ModuleSuffix { get; }
|
||||
|
||||
public static string RootBinaryPath { get; }
|
||||
|
||||
public static string GameBinaryPath { get; }
|
||||
|
||||
static Constants()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
ModulePrefix = "";
|
||||
ModuleSuffix = ".dll";
|
||||
GameBinaryPath = "/csgo/bin/win64/";
|
||||
RootBinaryPath = "/bin/win64";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
ModulePrefix = "lib";
|
||||
ModuleSuffix = ".so";
|
||||
GameBinaryPath = "/csgo/bin/linuxsteamrt64/";
|
||||
RootBinaryPath = "/bin/linuxsteamrt64/";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"""Current platform is "{RuntimeInformation.OSDescription}", but does not supported.""");
|
||||
}
|
||||
}
|
||||
}
|
||||
145
managed/CounterStrikeSharp.API/Core/CoreConfig.cs
Normal file
145
managed/CounterStrikeSharp.API/Core/CoreConfig.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using System.Collections.Generic;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using CounterStrikeSharp.API.Core.Logging;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializable instance of the CoreConfig
|
||||
/// </summary>
|
||||
internal sealed partial class CoreConfigData
|
||||
{
|
||||
[JsonPropertyName("PublicChatTrigger")]
|
||||
public IEnumerable<string> PublicChatTrigger { get; set; } = new HashSet<string>() { "!" };
|
||||
|
||||
[JsonPropertyName("SilentChatTrigger")]
|
||||
public IEnumerable<string> SilentChatTrigger { get; set; } = new HashSet<string>() { "/" };
|
||||
|
||||
[JsonPropertyName("FollowCS2ServerGuidelines")]
|
||||
public bool FollowCS2ServerGuidelines { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("PluginHotReloadEnabled")]
|
||||
public bool PluginHotReloadEnabled { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration related to the Core API.
|
||||
/// </summary>
|
||||
public partial class CoreConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// List of characters to use for public chat triggers.
|
||||
/// </summary>
|
||||
public static IEnumerable<string> PublicChatTrigger => _coreConfig.PublicChatTrigger;
|
||||
|
||||
/// <summary>
|
||||
/// List of characters to use for silent chat triggers.
|
||||
/// </summary>
|
||||
public static IEnumerable<string> SilentChatTrigger => _coreConfig.SilentChatTrigger;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Per <see href="http://blog.counter-strike.net/index.php/server_guidelines/"/>, certain plugin
|
||||
/// functionality will trigger all of the game server owner's Game Server Login Tokens
|
||||
/// (GSLTs) to get banned when executed on a Counter-Strike 2 game server.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Enabling this option will block plugins from using functionality that is known to cause this.
|
||||
///
|
||||
/// Note that this does NOT guarantee that you cannot
|
||||
///
|
||||
/// receive a ban.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Disable this option at your own risk.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static bool FollowCS2ServerGuidelines => _coreConfig.FollowCS2ServerGuidelines;
|
||||
|
||||
/// <summary>
|
||||
/// When enabled, plugins are automatically reloaded when their .dll file is updated.
|
||||
/// </summary>
|
||||
public static bool PluginHotReloadEnabled => _coreConfig.PluginHotReloadEnabled;
|
||||
}
|
||||
|
||||
public partial class CoreConfig : IStartupService
|
||||
{
|
||||
private static CoreConfigData _coreConfig = new CoreConfigData();
|
||||
|
||||
private readonly ILogger<CoreConfig> _logger;
|
||||
|
||||
private readonly string _coreConfigPath;
|
||||
|
||||
public CoreConfig(IScriptHostConfiguration scriptHostConfiguration, ILogger<CoreConfig> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_coreConfigPath = Path.Join(scriptHostConfiguration.ConfigsPath, "core.json");
|
||||
}
|
||||
|
||||
[RequiresPermissions("@css/config")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
private void ReloadCoreConfigCommand(CCSPlayerController? player, CommandInfo command)
|
||||
{
|
||||
Load();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
CommandUtils.AddStandaloneCommand("css_core_reload", "Reloads the core configuration file.",
|
||||
ReloadCoreConfigCommand);
|
||||
|
||||
if (!File.Exists(_coreConfigPath))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Core configuration could not be found at path \"{CoreConfigPath}\", fallback values will be used.",
|
||||
_coreConfigPath);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var data = JsonSerializer.Deserialize<CoreConfigData>(File.ReadAllText(_coreConfigPath),
|
||||
new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
_coreConfig = data;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load core configuration, fallback values will be used");
|
||||
}
|
||||
|
||||
_logger.LogInformation("Successfully loaded core configuration");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
@@ -76,7 +77,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
Application.Instance.Logger.LogError(e, "Error invoking callback");
|
||||
}
|
||||
});
|
||||
s_callback = dg;
|
||||
@@ -140,10 +141,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
ms_references.Remove(reference);
|
||||
|
||||
Console.BackgroundColor = ConsoleColor.Blue;
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine($"Removing function/callback reference: {reference}");
|
||||
Console.ResetColor();
|
||||
Application.Instance.Logger.LogDebug("Removing function/callback reference: {Reference}", reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
class LoadedGameData
|
||||
public class LoadedGameData
|
||||
{
|
||||
[JsonPropertyName("signatures")] public Signatures? Signatures { get; set; }
|
||||
[JsonPropertyName("offsets")] public Offsets? Offsets { get; set; }
|
||||
@@ -29,49 +32,88 @@ public class Offsets
|
||||
[JsonPropertyName("linux")] public int Linux { get; set; }
|
||||
}
|
||||
|
||||
public static class GameData
|
||||
public sealed class GameDataProvider : IStartupService
|
||||
{
|
||||
private static Dictionary<string, LoadedGameData> _methods;
|
||||
private readonly string _gameDataFilePath;
|
||||
public Dictionary<string,LoadedGameData> Methods;
|
||||
private readonly ILogger<GameDataProvider> _logger;
|
||||
|
||||
public static void Load(string gameDataPath)
|
||||
public GameDataProvider(IScriptHostConfiguration scriptHostConfiguration, ILogger<GameDataProvider> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_gameDataFilePath = Path.Join(scriptHostConfiguration.GameDataPath, "gamedata.json");
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
_methods = JsonSerializer.Deserialize<Dictionary<string, LoadedGameData>>(File.ReadAllText(gameDataPath));
|
||||
|
||||
Console.WriteLine($"Loaded game data with {_methods.Count} methods.");
|
||||
Methods = JsonSerializer.Deserialize<Dictionary<string, LoadedGameData>>(File.ReadAllText(_gameDataFilePath))!;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to load game data: {ex.ToString()}");
|
||||
_logger.LogError(ex, "Failed to load game data");
|
||||
}
|
||||
|
||||
_logger.LogInformation("Successfully loaded {Count} game data entries from {Path}", Methods.Count, _gameDataFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
public static class GameData
|
||||
{
|
||||
internal static GameDataProvider GameDataProvider { get; set; } = null!;
|
||||
public static string GetSignature(string key)
|
||||
{
|
||||
if (!_methods.ContainsKey(key)) throw new Exception($"Method {key} not found in gamedata.json");
|
||||
if (_methods[key].Signatures == null) throw new Exception($"No signatures found for {key} in gamedata.json");
|
||||
|
||||
var methodMetadata = _methods[key];
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
Application.Instance.Logger.LogDebug("Getting signature: {Key}", key);
|
||||
if (!GameDataProvider.Methods.ContainsKey(key))
|
||||
{
|
||||
return methodMetadata.Signatures!.Linux;
|
||||
throw new ArgumentException($"Method {key} not found in gamedata.json");
|
||||
}
|
||||
|
||||
return methodMetadata.Signatures!.Windows;
|
||||
var methodMetadata = GameDataProvider.Methods[key];
|
||||
if (methodMetadata.Signatures == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No signatures found for {key} in gamedata.json");
|
||||
}
|
||||
|
||||
string signature;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
signature = methodMetadata.Signatures?.Linux ?? throw new InvalidOperationException($"No Linux signature for {key} in gamedata.json");
|
||||
}
|
||||
else
|
||||
{
|
||||
signature = methodMetadata.Signatures?.Windows ?? throw new InvalidOperationException($"No Windows signature for {key} in gamedata.json");
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
public static int GetOffset(string key)
|
||||
{
|
||||
if (!_methods.ContainsKey(key)) throw new Exception($"Method {key} not found in gamedata.json");
|
||||
if (_methods[key].Offsets == null) throw new Exception($"No offsets found for {key} in gamedata.json");
|
||||
|
||||
var methodMetadata = _methods[key];
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
if (!GameDataProvider.Methods.ContainsKey(key))
|
||||
{
|
||||
return methodMetadata.Offsets!.Linux;
|
||||
throw new Exception($"Method {key} not found in gamedata.json");
|
||||
}
|
||||
|
||||
return methodMetadata.Offsets!.Windows;
|
||||
var methodMetadata = GameDataProvider.Methods[key];
|
||||
|
||||
if (methodMetadata.Offsets == null)
|
||||
{
|
||||
throw new Exception($"No offsets found for {key} in gamedata.json");
|
||||
}
|
||||
|
||||
int offset;
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
offset = methodMetadata.Offsets?.Linux ?? throw new InvalidOperationException($"No Linux offset for {key} in gamedata.json");
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = methodMetadata.Offsets?.Windows ?? throw new InvalidOperationException($"No Windows offset for {key} in gamedata.json");
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Commands;
|
||||
using CounterStrikeSharp.API.Modules.Menu;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
public sealed class GlobalContext
|
||||
{
|
||||
private static GlobalContext _instance = null;
|
||||
public static GlobalContext Instance => _instance;
|
||||
|
||||
private DirectoryInfo rootDir;
|
||||
|
||||
private readonly List<PluginContext> _loadedPlugins = new();
|
||||
|
||||
public GlobalContext()
|
||||
{
|
||||
rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
~GlobalContext()
|
||||
{
|
||||
foreach (var plugin in _loadedPlugins)
|
||||
{
|
||||
plugin.Unload();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnNativeUnload()
|
||||
{
|
||||
foreach (var plugin in _loadedPlugins)
|
||||
{
|
||||
plugin.Unload();
|
||||
}
|
||||
}
|
||||
public void InitGlobalContext()
|
||||
{
|
||||
Console.WriteLine("Loading GameData from \"gamedata/gamedata.json\"");
|
||||
GameData.Load(Path.Combine(rootDir.FullName, "gamedata", "gamedata.json"));
|
||||
|
||||
Console.WriteLine("Loading Admins from \"configs/admins.json\"");
|
||||
AdminManager.Load(Path.Combine(rootDir.FullName, "configs", "admins.json"));
|
||||
|
||||
for (int i = 1; i <= 9; i++)
|
||||
{
|
||||
CommandUtils.AddStandaloneCommand("css_" + i, "Command Key Handler", (player, info) =>
|
||||
{
|
||||
if (player == null) return;
|
||||
var key = Convert.ToInt32(info.GetArg(0).Split("_")[1]);
|
||||
ChatMenus.OnKeyPress(player, key);
|
||||
});
|
||||
}
|
||||
|
||||
Console.WriteLine("Loading C# plugins...");
|
||||
int pluginCount = LoadAllPlugins();
|
||||
Console.WriteLine($"All managed modules were loaded. {pluginCount} plugins loaded.");
|
||||
|
||||
RegisterPluginCommands();
|
||||
}
|
||||
|
||||
public void LoadPlugin(string path)
|
||||
{
|
||||
var existingPlugin = FindPluginByModulePath(path);
|
||||
if (existingPlugin != null)
|
||||
{
|
||||
throw new Exception("Plugin is already loaded.");
|
||||
}
|
||||
|
||||
var plugin = new PluginContext(path, _loadedPlugins.Select(x => x.PluginId).DefaultIfEmpty(0).Max() + 1);
|
||||
plugin.Load();
|
||||
_loadedPlugins.Add(plugin);
|
||||
}
|
||||
|
||||
public int LoadAllPlugins()
|
||||
{
|
||||
DirectoryInfo modules_directory_info;
|
||||
try
|
||||
{
|
||||
modules_directory_info = new DirectoryInfo(Path.Combine(rootDir.FullName, "plugins"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"Unable to access .NET modules directory: " + e.GetType().ToString() + " " + e.Message);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DirectoryInfo[] proper_modules_directories;
|
||||
try
|
||||
{
|
||||
proper_modules_directories = modules_directory_info.GetDirectories();
|
||||
}
|
||||
catch
|
||||
{
|
||||
proper_modules_directories = new DirectoryInfo[0];
|
||||
}
|
||||
|
||||
var filePaths = proper_modules_directories
|
||||
.Where(d => d.GetFiles().Any((f) => f.Name == d.Name + ".dll"))
|
||||
.Select(d => d.GetFiles().First((f) => f.Name == d.Name + ".dll").FullName)
|
||||
.ToArray();
|
||||
|
||||
foreach (var path in filePaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadPlugin(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Failed to load plugin {path} with error {e}");
|
||||
}
|
||||
}
|
||||
|
||||
return _loadedPlugins.Count;
|
||||
}
|
||||
|
||||
public void UnloadAllPlugins()
|
||||
{
|
||||
foreach (var plugin in _loadedPlugins)
|
||||
{
|
||||
plugin.Unload();
|
||||
_loadedPlugins.Remove(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private PluginContext? FindPluginByType(Type moduleClass)
|
||||
{
|
||||
return _loadedPlugins.FirstOrDefault(x => x.PluginType == moduleClass);
|
||||
}
|
||||
|
||||
private PluginContext? FindPluginById(int id)
|
||||
{
|
||||
return _loadedPlugins.FirstOrDefault(x => x.PluginId == id);
|
||||
}
|
||||
|
||||
private PluginContext? FindPluginByModuleName(string name)
|
||||
{
|
||||
return _loadedPlugins.FirstOrDefault(x => x.Name == name);
|
||||
}
|
||||
|
||||
private PluginContext? FindPluginByModulePath(string path)
|
||||
{
|
||||
return _loadedPlugins.FirstOrDefault(x => x.PluginPath == path);
|
||||
}
|
||||
|
||||
private PluginContext? FindPluginByIdOrName(string query)
|
||||
{
|
||||
|
||||
PluginContext? plugin = null;
|
||||
if (Int32.TryParse(query, out var pluginNumber))
|
||||
{
|
||||
plugin = FindPluginById(pluginNumber);
|
||||
if (plugin != null) return plugin;
|
||||
}
|
||||
|
||||
plugin = FindPluginByModuleName(query);
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
[RequiresPermissions("can_execute_css_commands")]
|
||||
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
private void OnCSSCommand(CCSPlayerController? caller, CommandInfo info)
|
||||
{
|
||||
var currentVersion = Api.GetVersion();
|
||||
|
||||
info.ReplyToCommand(" CounterStrikeSharp was created and is maintained by Michael \"roflmuffin\" Wilson.\n" +
|
||||
" Counter-Strike Sharp uses code borrowed from SourceMod, Source.Python, FiveM, Saul Rennison and CS2Fixes.\n" +
|
||||
" See ACKNOWLEDGEMENTS.md for more information.\n" +
|
||||
" Current API Version: " + currentVersion, true);
|
||||
return;
|
||||
}
|
||||
|
||||
[RequiresPermissions("can_execute_css_commands")]
|
||||
[CommandHelper(minArgs: 1,
|
||||
usage: "[option]\n" +
|
||||
" list - List all plugins currently loaded.\n" +
|
||||
" start / load - Loads a plugin not currently loaded.\n" +
|
||||
" stop / unload - Unloads a plugin currently loaded.\n" +
|
||||
" restart / reload - Reloads a plugin currently loaded.",
|
||||
whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
|
||||
private void OnCSSPluginCommand(CCSPlayerController? caller, CommandInfo info)
|
||||
{
|
||||
switch (info.GetArg(1))
|
||||
{
|
||||
case "list":
|
||||
{
|
||||
info.ReplyToCommand($" List of all plugins currently loaded by CounterStrikeSharp: {_loadedPlugins.Count} plugins loaded.", true);
|
||||
|
||||
foreach (var plugin in _loadedPlugins)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendFormat(" [#{0}]: \"{1}\" ({2})", plugin.PluginId, plugin.Name, plugin.Version);
|
||||
if (!string.IsNullOrEmpty(plugin.Author)) sb.AppendFormat(" by {0}", plugin.Author);
|
||||
if (!string.IsNullOrEmpty(plugin.Description))
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.Append(" ");
|
||||
sb.Append(plugin.Description);
|
||||
}
|
||||
info.ReplyToCommand(sb.ToString(), true);
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "start":
|
||||
case "load":
|
||||
{
|
||||
if (info.ArgCount < 2)
|
||||
{
|
||||
info.ReplyToCommand("Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n", true);
|
||||
break;
|
||||
}
|
||||
|
||||
// If our arugment doesn't end in ".dll" - try and construct a path similar to PluginName/PluginName.dll.
|
||||
// We'll assume we have a full path if we have ".dll".
|
||||
var path = info.GetArg(2);
|
||||
if (!path.EndsWith(".dll"))
|
||||
{
|
||||
path = Path.Combine(rootDir.FullName, $"plugins/{path}/{path}.dll");
|
||||
}
|
||||
else
|
||||
{
|
||||
path = Path.Combine(rootDir.FullName, path);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
LoadPlugin(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Failed to load plugin {path} with error {e}");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "stop":
|
||||
case "unload":
|
||||
{
|
||||
if (info.ArgCount < 2)
|
||||
{
|
||||
info.ReplyToCommand("Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n", true);
|
||||
break;
|
||||
}
|
||||
|
||||
var pluginIdentifier = info.GetArg(2);
|
||||
PluginContext? plugin = FindPluginByIdOrName(pluginIdentifier);
|
||||
if (plugin == null)
|
||||
{
|
||||
info.ReplyToCommand($"Could not unload plugin \"{pluginIdentifier}\")", true);
|
||||
break;
|
||||
}
|
||||
plugin.Unload();
|
||||
_loadedPlugins.Remove(plugin);
|
||||
break;
|
||||
}
|
||||
|
||||
case "restart":
|
||||
case "reload":
|
||||
{
|
||||
if (info.ArgCount < 2)
|
||||
{
|
||||
info.ReplyToCommand("Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n", true);
|
||||
break;
|
||||
}
|
||||
|
||||
var pluginIdentifier = info.GetArg(2);
|
||||
var plugin = FindPluginByIdOrName(pluginIdentifier);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
info.ReplyToCommand($"Could not reload plugin \"{pluginIdentifier}\")", true);
|
||||
break;
|
||||
}
|
||||
plugin.Unload(true);
|
||||
plugin.Load(true);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
info.ReplyToCommand("Valid usage: css_plugins [option]\n" +
|
||||
" list - List all plugins currently loaded.\n" +
|
||||
" start / load - Loads a plugin not currently loaded.\n" +
|
||||
" stop / unload - Unloads a plugin currently loaded.\n" +
|
||||
" restart / reload - Reloads a plugin currently loaded."
|
||||
, true);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void RegisterPluginCommands()
|
||||
{
|
||||
CommandUtils.AddStandaloneCommand("css", "Counter-Strike Sharp options.", OnCSSCommand);
|
||||
CommandUtils.AddStandaloneCommand("css_plugins", "Counter-Strike Sharp plugin options.", OnCSSPluginCommand);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -22,53 +22,12 @@ using System.Security;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
public class MethodAttribute<T> where T : Attribute
|
||||
{
|
||||
public MethodAttribute(T attribute, MethodInfo method)
|
||||
{
|
||||
Attribute = attribute;
|
||||
Method = method;
|
||||
}
|
||||
|
||||
public T Attribute;
|
||||
public MethodInfo Method;
|
||||
}
|
||||
|
||||
public static class Helpers
|
||||
{
|
||||
private static MethodAttribute<T>[] FindMethodAttributes<T>(BasePlugin plugin) where T: Attribute
|
||||
{
|
||||
return plugin
|
||||
.GetType()
|
||||
.GetMethods()
|
||||
.Where(m => m.GetCustomAttributes(typeof(T), false).Length > 0)
|
||||
.Select(x => new MethodAttribute<T>(x.GetCustomAttribute<T>(), x))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private const string dllPath = "counterstrikesharp";
|
||||
|
||||
[SecurityCritical]
|
||||
[DllImport(dllPath, EntryPoint = "InvokeNative")]
|
||||
public static extern void InvokeNative(IntPtr ptr);
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
// Used by .NET Host in C++ to initiate loading
|
||||
public static int LoadAllPlugins()
|
||||
{
|
||||
try
|
||||
{
|
||||
var globalContext = new GlobalContext();
|
||||
globalContext.InitGlobalContext();
|
||||
return 1;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.StackTrace);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void Callback();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace CounterStrikeSharp.API.Core.Hosting;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Provides information about the CounterStrikeSharp host configuration.
|
||||
/// </summary>
|
||||
public interface IScriptHostConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the absolute path to the directory that contains CounterStrikeSharp files.
|
||||
/// e.g. /game/csgo/addons/counterstrikesharp
|
||||
/// </summary>
|
||||
string RootPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute path to the directory that contains CounterStrikeSharp plugins.
|
||||
/// e.g. /game/csgo/addons/counterstrikesharp/plugins
|
||||
/// </summary>
|
||||
string PluginPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute path to the directory that contains CounterStrikeSharp configs.
|
||||
/// e.g. /game/csgo/addons/counterstrikesharp/configs
|
||||
/// </summary>
|
||||
string ConfigsPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute path to the directory that contains CounterStrikeSharp game data.
|
||||
/// e.g. /game/csgo/addons/counterstrikesharp/gamedata
|
||||
/// </summary>
|
||||
string GameDataPath { get; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Hosting;
|
||||
|
||||
internal sealed class ScriptHostConfiguration : IScriptHostConfiguration
|
||||
{
|
||||
public string RootPath { get; }
|
||||
public string PluginPath { get; }
|
||||
public string ConfigsPath { get; }
|
||||
public string GameDataPath { get; }
|
||||
|
||||
public ScriptHostConfiguration(IHostEnvironment hostEnvironment)
|
||||
{
|
||||
RootPath = Path.Join(new[] { hostEnvironment.ContentRootPath });
|
||||
PluginPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "plugins" });
|
||||
ConfigsPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "configs" });
|
||||
GameDataPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "gamedata" });
|
||||
}
|
||||
}
|
||||
@@ -14,28 +14,30 @@
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface which every CounterStrikeSharp plugin must implement. Module will be created with parameterless constructor and then Load method will be called.
|
||||
/// </summary>
|
||||
public interface IPlugin
|
||||
public interface IPlugin : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the plugin.
|
||||
/// </summary>
|
||||
string ModuleName
|
||||
{
|
||||
get;
|
||||
}
|
||||
string ModuleName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Module version.
|
||||
/// </summary>
|
||||
string ModuleVersion
|
||||
{
|
||||
get;
|
||||
}
|
||||
string ModuleVersion { get; }
|
||||
|
||||
string ModuleAuthor { get; }
|
||||
|
||||
string ModuleDescription { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by CounterStrikeSharp on plugin load and should be treated as plugin constructor.
|
||||
@@ -48,5 +50,13 @@ namespace CounterStrikeSharp.API.Core
|
||||
/// Event handlers, listeners etc. will automatically be deregistered.
|
||||
/// </summary>
|
||||
void Unload(bool hotReload);
|
||||
|
||||
string ModulePath { get; internal set; }
|
||||
|
||||
ILogger Logger { get; set; }
|
||||
|
||||
void RegisterAllAttributes(object instance);
|
||||
|
||||
void InitializeConfig(object instance, Type pluginType);
|
||||
}
|
||||
}
|
||||
32
managed/CounterStrikeSharp.API/Core/IPluginConfig.cs
Normal file
32
managed/CounterStrikeSharp.API/Core/IPluginConfig.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface that describes a plugin configuration.
|
||||
/// </summary>
|
||||
public interface IPluginConfig<T> where T: IBasePluginConfig, new()
|
||||
{
|
||||
T Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the `ConfigManager` has parsed the configuration file for this plugin
|
||||
/// </summary>
|
||||
/// <param name="config">Parsed config instance</param>
|
||||
public void OnConfigParsed(T config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a service collection configuration for a plugin.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public interface IPluginServiceCollection<T> where T : IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to configure services exposed for dependency injection.
|
||||
/// </summary>
|
||||
public void ConfigureServices(IServiceCollection serviceCollection);
|
||||
}
|
||||
6
managed/CounterStrikeSharp.API/Core/IStartupService.cs
Normal file
6
managed/CounterStrikeSharp.API/Core/IStartupService.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public interface IStartupService
|
||||
{
|
||||
public void Load();
|
||||
}
|
||||
@@ -125,6 +125,18 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
return new InputArgument(value);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
public static implicit operator InputArgument(NativeObject value)
|
||||
{
|
||||
return new InputArgument(value.Handle);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
public static implicit operator InputArgument(NativeEntity value)
|
||||
{
|
||||
return new InputArgument(value.Handle);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
public static unsafe implicit operator InputArgument(void* value)
|
||||
|
||||
@@ -126,5 +126,26 @@ namespace CounterStrikeSharp.API.Core
|
||||
/// <param name="hostname">New hostname of the server</param>
|
||||
[ListenerName("OnHostNameChanged")]
|
||||
public delegate void OnHostNameChanged(string hostname);
|
||||
|
||||
/// <summary>
|
||||
/// Called before the server enters fatal shutdown.
|
||||
/// </summary>
|
||||
[ListenerName("OnPreFatalShutdown")]
|
||||
public delegate void OnServerPreFatalShutdown();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the server is in a loading stage.
|
||||
/// </summary>
|
||||
/// <param name="frameTime"></param>
|
||||
[ListenerName("OnUpdateWhenNotInGame")]
|
||||
public delegate void OnUpdateWhenNotInGame(float frameTime);
|
||||
|
||||
/// <summary>
|
||||
/// Called before the world updates.
|
||||
/// This seems to be called even when the server is hibernating.
|
||||
/// </summary>
|
||||
/// <param name="simulating"><see langword="true"/> if simulating, <see langword="false"/> otherwise</param>
|
||||
[ListenerName("OnServerPreWorldUpdate")]
|
||||
public delegate void OnServerPreWorldUpdate(bool simulating);
|
||||
}
|
||||
}
|
||||
|
||||
41
managed/CounterStrikeSharp.API/Core/Logging/CoreLogging.cs
Normal file
41
managed/CounterStrikeSharp.API/Core/Logging/CoreLogging.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Logging;
|
||||
|
||||
public static class CoreLogging
|
||||
{
|
||||
public static ILoggerFactory Factory { get; private set; }
|
||||
private static Logger? SerilogLogger { get; set; }
|
||||
|
||||
public static void AddCoreLogging(this ILoggingBuilder builder, string contentRoot)
|
||||
{
|
||||
if (SerilogLogger == null)
|
||||
{
|
||||
SerilogLogger = new LoggerConfiguration()
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.With<SourceContextEnricher>()
|
||||
.WriteTo.Console(
|
||||
outputTemplate:
|
||||
"{Timestamp:HH:mm:ss} [{Level:u4}] (cssharp:{SourceContext}) {Message:lj}{NewLine}{Exception}")
|
||||
.WriteTo.File(Path.Join(new[] { contentRoot, "logs", $"log-cssharp.txt" }),
|
||||
rollingInterval: RollingInterval.Day,
|
||||
outputTemplate:
|
||||
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] (cssharp:{SourceContext}) {Message:lj}{NewLine}{Exception}")
|
||||
.WriteTo.File(Path.Join(new[] { contentRoot, "logs", $"log-all.txt" }),
|
||||
rollingInterval: RollingInterval.Day, shared: true,
|
||||
outputTemplate:
|
||||
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] (cssharp:{SourceContext}) {Message:lj}{NewLine}{Exception}")
|
||||
.CreateLogger();
|
||||
|
||||
Factory =
|
||||
LoggerFactory.Create(builder => { builder.AddSerilog(SerilogLogger); });
|
||||
}
|
||||
|
||||
builder.AddSerilog(SerilogLogger);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using CounterStrikeSharp.API.Core.Plugin;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Logging;
|
||||
|
||||
public class PluginNameEnricher : ILogEventEnricher
|
||||
{
|
||||
public const string PropertyName = "PluginName";
|
||||
|
||||
public PluginNameEnricher(PluginContext pluginContext)
|
||||
{
|
||||
Context = pluginContext;
|
||||
}
|
||||
|
||||
public PluginContext Context { get; }
|
||||
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
var property = propertyFactory.CreateProperty(PropertyName, Context.Plugin.ModuleName);
|
||||
logEvent.AddPropertyIfAbsent(property);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Linq;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Logging;
|
||||
|
||||
public class SourceContextEnricher : ILogEventEnricher
|
||||
{
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
if (logEvent.Properties.TryGetValue("SourceContext", out var property))
|
||||
{
|
||||
var scalarValue = property as ScalarValue;
|
||||
var value = scalarValue?.Value as string;
|
||||
|
||||
if (value?.StartsWith("CounterStrikeSharp") ?? false)
|
||||
{
|
||||
var lastElement = value.Split(".").LastOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace(lastElement))
|
||||
{
|
||||
logEvent.AddOrUpdateProperty(new LogEventProperty("SourceContext", new ScalarValue(lastElement)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,11 @@ public partial class CBaseEntity
|
||||
Handle, position.Handle, angles.Handle, velocity.Handle);
|
||||
}
|
||||
|
||||
public void DispatchSpawn()
|
||||
{
|
||||
VirtualFunctions.CBaseEntity_DispatchSpawn(Handle, IntPtr.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsOrigin;
|
||||
/// </summary>
|
||||
|
||||
@@ -18,9 +18,10 @@ using CounterStrikeSharp.API.Modules.Memory;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CGlowProperty
|
||||
public partial class CBaseModelEntity
|
||||
{
|
||||
// m_bGlowing
|
||||
public ref bool IsGlowing => ref Schema.GetRef<bool>(this.Handle, "CGlowProperty", "m_bGlowing");
|
||||
|
||||
}
|
||||
public void SetModel(string model)
|
||||
{
|
||||
VirtualFunctions.SetModel(Handle, model);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,11 @@ namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CBasePlayerPawn
|
||||
{
|
||||
/// <summary>
|
||||
/// Force player suicide
|
||||
/// </summary>
|
||||
/// <param name="explode"></param>
|
||||
/// <param name="force"></param>
|
||||
public void CommitSuicide(bool explode, bool force)
|
||||
{
|
||||
VirtualFunction.CreateVoid<IntPtr, bool, bool>(Handle, GameData.GetOffset("CBasePlayerPawn_CommitSuicide"))(Handle, explode, force);
|
||||
|
||||
31
managed/CounterStrikeSharp.API/Core/Model/CCSGameRules.cs
Normal file
31
managed/CounterStrikeSharp.API/Core/Model/CCSGameRules.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CCSGameRules
|
||||
{
|
||||
/// <summary>
|
||||
/// Terminate the round with the given delay and reason.
|
||||
/// </summary>
|
||||
public void TerminateRound(float delay, RoundEndReason roundEndReason)
|
||||
{
|
||||
VirtualFunctions.TerminateRound(Handle, roundEndReason, delay);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
using System;
|
||||
|
||||
using CounterStrikeSharp.API.Modules.Cvars;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
@@ -10,8 +14,7 @@ public partial class CCSPlayerController
|
||||
{
|
||||
get
|
||||
{
|
||||
if (EntityIndex == null) return null;
|
||||
return NativeAPI.GetUseridFromIndex((int)this.EntityIndex.Value.Value);
|
||||
return NativeAPI.GetUseridFromIndex((int)this.Index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +27,20 @@ public partial class CCSPlayerController
|
||||
return VirtualFunctions.GiveNamedItem(PlayerPawn.Value.ItemServices.Handle, item, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
public IntPtr GiveNamedItem(CsItem item)
|
||||
{
|
||||
string? itemString = EnumUtils.GetEnumMemberAttributeValue(item);
|
||||
if (string.IsNullOrWhiteSpace(itemString))
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
return this.GiveNamedItem(itemString);
|
||||
}
|
||||
|
||||
public void PrintToConsole(string message)
|
||||
{
|
||||
NativeAPI.PrintToConsole((int)EntityIndex.Value.Value, message);
|
||||
NativeAPI.PrintToConsole((int)Index, $"{message}\n\0");
|
||||
}
|
||||
|
||||
public void PrintToChat(string message)
|
||||
@@ -38,18 +52,73 @@ public partial class CCSPlayerController
|
||||
{
|
||||
VirtualFunctions.ClientPrint(this.Handle, HudDestination.Center, message, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
public void PrintToCenterHtml(string message) => PrintToCenterHtml(message, 5);
|
||||
|
||||
public void PrintToCenterHtml(string message)
|
||||
public void PrintToCenterHtml(string message, int duration)
|
||||
{
|
||||
var @event = new EventShowSurvivalRespawnStatus(true)
|
||||
{
|
||||
LocToken = message,
|
||||
Duration = 5,
|
||||
Duration = duration,
|
||||
Userid = this
|
||||
};
|
||||
@event.FireEvent(false);
|
||||
@event.FireEventToClient(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drops the active player weapon on the ground.
|
||||
/// </summary>
|
||||
public void DropActiveWeapon()
|
||||
{
|
||||
if (!PlayerPawn.IsValid) return;
|
||||
if (!PlayerPawn.Value.IsValid) return;
|
||||
if (PlayerPawn.Value.ItemServices == null) return;
|
||||
if (PlayerPawn.Value.WeaponServices == null) return;
|
||||
if (!PlayerPawn.Value.WeaponServices.ActiveWeapon.IsValid) return;
|
||||
|
||||
CCSPlayer_ItemServices itemServices = new CCSPlayer_ItemServices(PlayerPawn.Value.ItemServices.Handle);
|
||||
CCSPlayer_WeaponServices weponServices = new CCSPlayer_WeaponServices(PlayerPawn.Value.WeaponServices.Handle);
|
||||
itemServices.DropActivePlayerWeapon(weponServices.ActiveWeapon.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes every weapon from the player.
|
||||
/// </summary>
|
||||
public void RemoveWeapons()
|
||||
{
|
||||
if (!PlayerPawn.IsValid) return;
|
||||
if (!PlayerPawn.Value.IsValid) return;
|
||||
if (PlayerPawn.Value.ItemServices == null) return;
|
||||
|
||||
CCSPlayer_ItemServices itemServices = new CCSPlayer_ItemServices(PlayerPawn.Value.ItemServices.Handle);
|
||||
itemServices.RemoveWeapons();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force player suicide
|
||||
/// </summary>
|
||||
/// <param name="explode"></param>
|
||||
/// <param name="force"></param>
|
||||
public void CommitSuicide(bool explode, bool force)
|
||||
{
|
||||
if (!PlayerPawn.IsValid) return;
|
||||
if (!PlayerPawn.Value.IsValid) return;
|
||||
|
||||
PlayerPawn.Value.CommitSuicide(explode, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Respawn player
|
||||
/// </summary>
|
||||
public void Respawn()
|
||||
{
|
||||
if (!PlayerPawn.IsValid) return;
|
||||
if (!PlayerPawn.Value.IsValid) return;
|
||||
|
||||
VirtualFunctions.CCSPlayerPawn_Respawn(PlayerPawn.Value.Handle);
|
||||
VirtualFunction.CreateVoid<IntPtr>(Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(Handle);
|
||||
}
|
||||
|
||||
public bool IsBot => ((PlayerFlags)Flags).HasFlag(PlayerFlags.FL_FAKECLIENT);
|
||||
|
||||
@@ -75,8 +144,94 @@ public partial class CCSPlayerController
|
||||
team);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a ConVar value for given player
|
||||
/// </summary>
|
||||
/// <param name="conVar">Name of the convar to retrieve</param>
|
||||
/// <returns>ConVar string value</returns>
|
||||
public string GetConVarValue(string conVar)
|
||||
{
|
||||
return NativeAPI.GetClientConvarValue(this.Slot, conVar);
|
||||
}
|
||||
|
||||
public string GetConVarValue(ConVar? conVar)
|
||||
{
|
||||
if (conVar == null)
|
||||
{
|
||||
throw new Exception("Invalid convar passed to 'GetConVarValue'");
|
||||
}
|
||||
|
||||
return GetConVarValue(conVar.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a ConVar value on a fake client (bot).
|
||||
/// </summary>
|
||||
/// <param name="conVar">Console variable name</param>
|
||||
/// <param name="value">String value to set</param>
|
||||
/// <exception cref="InvalidOperationException">Player is not a bot</exception>
|
||||
public void SetFakeClientConVar(string conVar, string value)
|
||||
{
|
||||
if (!IsBot)
|
||||
{
|
||||
throw new InvalidOperationException("'SetFakeClientConVar' can only be called for fake clients (bots)");
|
||||
}
|
||||
|
||||
NativeAPI.SetFakeClientConvarValue(this.Slot, conVar, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="SetFakeClientConVar(string,string)"/>
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException"><paramref name="conVar"/> is <see langword="null"/></exception>
|
||||
/// <inheritdoc cref="SetFakeClientConVar(string,string)" select="exception"/>
|
||||
public void SetFakeClientConVar(ConVar conVar, string value)
|
||||
{
|
||||
if (conVar == null)
|
||||
{
|
||||
throw new ArgumentException("Invalid convar passed to 'SetFakeClientConVar'");
|
||||
}
|
||||
|
||||
SetFakeClientConVar(conVar.Name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active pawns button state. Will work even if the player is dead or observing.
|
||||
/// </summary>
|
||||
public PlayerButtons Buttons => (PlayerButtons)Pawn.Value.MovementServices!.Buttons.ButtonStates[0];
|
||||
|
||||
public void ExecuteClientCommand(string command) => NativeAPI.IssueClientCommand(Slot, command);
|
||||
|
||||
public int Slot => (int)Index - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the authorized SteamID of this user which has been validated with the SteamAPI.
|
||||
/// </summary>
|
||||
public SteamID? AuthorizedSteamID
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!this.IsValid) return null;
|
||||
var authorizedSteamId = NativeAPI.GetPlayerAuthorizedSteamid(this.Slot);
|
||||
if ((long)authorizedSteamId == -1) return null;
|
||||
|
||||
return (SteamID)authorizedSteamId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the IP address (and possibly port) of this player.
|
||||
/// <remarks>Returns 127.0.0.1 if the player is a bot.</remarks>
|
||||
/// </summary>
|
||||
public string? IpAddress
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!this.IsValid) return null;
|
||||
var ipAddress = NativeAPI.GetPlayerIpAddress(this.Slot);
|
||||
if (string.IsNullOrWhiteSpace(ipAddress)) return null;
|
||||
|
||||
return ipAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
managed/CounterStrikeSharp.API/Core/Model/CCSPlayerPawn.cs
Normal file
19
managed/CounterStrikeSharp.API/Core/Model/CCSPlayerPawn.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CCSPlayerPawn
|
||||
{
|
||||
/// <summary>
|
||||
/// Respawn player
|
||||
/// </summary>
|
||||
public void Respawn()
|
||||
{
|
||||
if (!Controller.IsValid) return;
|
||||
if (!Controller.Value.IsValid) return;
|
||||
|
||||
VirtualFunctions.CCSPlayerPawn_Respawn(Handle);
|
||||
VirtualFunction.CreateVoid<IntPtr>(Controller.Value.Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(Controller.Value.Handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CCSPlayer_ItemServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Drops the active player weapon on the ground.
|
||||
/// </summary>
|
||||
public void DropActivePlayerWeapon(CBasePlayerWeapon activeWeapon)
|
||||
{
|
||||
VirtualFunction.CreateVoid<nint, nint>(Handle, GameData.GetOffset("CCSPlayer_ItemServices_DropActivePlayerWeapon"))(Handle, activeWeapon.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes every weapon from the player.
|
||||
/// </summary>
|
||||
public void RemoveWeapons()
|
||||
{
|
||||
VirtualFunction.CreateVoid<nint>(Handle, GameData.GetOffset("CCSPlayer_ItemServices_RemoveWeapons"))(Handle);
|
||||
}
|
||||
}
|
||||
@@ -12,23 +12,36 @@ namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CEntityInstance : IEquatable<CEntityInstance>
|
||||
{
|
||||
public bool IsValid => Handle != IntPtr.Zero;
|
||||
public CEntityInstance(IntPtr pointer) : base(pointer)
|
||||
{
|
||||
}
|
||||
|
||||
public CEntityIndex? EntityIndex => IsValid ? Entity?.EntityHandle.Index : null;
|
||||
public CEntityInstance(uint rawHandle) : base(rawHandle)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that the entity handle is valid and the handle points to a valid entity
|
||||
/// </summary>
|
||||
public bool IsValid => EntityHandle.IsValid && Handle != IntPtr.Zero;
|
||||
|
||||
[Obsolete("Use Index instead", true)]
|
||||
public CEntityIndex? EntityIndex => new CEntityIndex(EntityHandle.Index);
|
||||
|
||||
public uint Index => EntityHandle.Index;
|
||||
|
||||
public string DesignerName => IsValid ? Entity?.DesignerName : null;
|
||||
|
||||
public void Remove() => VirtualFunctions.UTIL_Remove(this.Handle);
|
||||
|
||||
|
||||
|
||||
public bool Equals(CEntityInstance? other)
|
||||
{
|
||||
return this.Handle == other?.Handle;
|
||||
return this.EntityHandle.Equals(other?.EntityHandle);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return ReferenceEquals(this, obj) || obj is CEntityInstance other && Equals(other);
|
||||
return obj is CEntityInstance other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
@@ -50,5 +63,5 @@ public partial class CEntityInstance : IEquatable<CEntityInstance>
|
||||
public partial class CEntityIdentity
|
||||
{
|
||||
public unsafe CEntityInstance EntityInstance => new(Unsafe.Read<IntPtr>((void*)Handle));
|
||||
public unsafe CHandle<CEntityInstance> EntityHandle => new(Handle + 0x10);
|
||||
public unsafe CHandle<CEntityInstance> EntityHandle => new(this.Handle + 0x10);
|
||||
}
|
||||
30
managed/CounterStrikeSharp.API/Core/Model/CGameSceneNode.cs
Normal file
30
managed/CounterStrikeSharp.API/Core/Model/CGameSceneNode.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CGameSceneNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="CSkeletonInstance"/> instance from the node.
|
||||
/// </summary>
|
||||
public CSkeletonInstance GetSkeletonInstance()
|
||||
{
|
||||
return new CSkeletonInstance(VirtualFunction.Create<nint, nint>(Handle, GameData.GetOffset("CGameSceneNode_GetSkeletonInstance"))(Handle));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CPlayerPawnComponent
|
||||
{
|
||||
public PointerTo<CBasePlayerPawn> Pawn => new PointerTo<CBasePlayerPawn>(this.Handle + 0x30);
|
||||
}
|
||||
@@ -355,6 +355,13 @@ public enum BrushSolidities_e : uint
|
||||
BRUSHSOLID_ALWAYS = 0x2,
|
||||
}
|
||||
|
||||
public enum C4LightEffect_t : uint
|
||||
{
|
||||
eLightEffectNone = 0x0,
|
||||
eLightEffectDropped = 0x1,
|
||||
eLightEffectThirdPersonHeld = 0x2,
|
||||
}
|
||||
|
||||
public enum CAnimationGraphVisualizerPrimitiveType : uint
|
||||
{
|
||||
ANIMATIONGRAPHVISUALIZERPRIMITIVETYPE_Text = 0x0,
|
||||
@@ -2773,6 +2780,7 @@ public enum TakeDamageFlags_t : uint
|
||||
DFLAG_RADIUS_DMG = 0x400,
|
||||
DMG_LASTDFLAG = 0x400,
|
||||
DFLAG_IGNORE_ARMOR = 0x800,
|
||||
DFLAG_SUPPRESS_UTILREMOVE = 0x1000,
|
||||
}
|
||||
|
||||
public enum TextureRepetitionMode_t : uint
|
||||
@@ -3927,6 +3935,9 @@ public partial class CBaseCSGrenadeProjectile : CBaseGrenade
|
||||
{
|
||||
public CBaseCSGrenadeProjectile (IntPtr pointer) : base(pointer) {}
|
||||
|
||||
// m_vInitialPosition
|
||||
public Vector InitialPosition => Schema.GetDeclaredClass<Vector>(this.Handle, "CBaseCSGrenadeProjectile", "m_vInitialPosition");
|
||||
|
||||
// m_vInitialVelocity
|
||||
public Vector InitialVelocity => Schema.GetDeclaredClass<Vector>(this.Handle, "CBaseCSGrenadeProjectile", "m_vInitialVelocity");
|
||||
|
||||
@@ -3972,6 +3983,12 @@ public partial class CBaseCSGrenadeProjectile : CBaseGrenade
|
||||
// m_nTicksAtZeroVelocity
|
||||
public ref Int32 TicksAtZeroVelocity => ref Schema.GetRef<Int32>(this.Handle, "CBaseCSGrenadeProjectile", "m_nTicksAtZeroVelocity");
|
||||
|
||||
// m_bHasEverHitPlayer
|
||||
public ref bool HasEverHitPlayer => ref Schema.GetRef<bool>(this.Handle, "CBaseCSGrenadeProjectile", "m_bHasEverHitPlayer");
|
||||
|
||||
// m_bClearFromPlayers
|
||||
public ref bool ClearFromPlayers => ref Schema.GetRef<bool>(this.Handle, "CBaseCSGrenadeProjectile", "m_bClearFromPlayers");
|
||||
|
||||
}
|
||||
|
||||
public partial class CBaseDMStart : CPointEntity
|
||||
@@ -7099,8 +7116,8 @@ public partial class CCSGameRules : CTeamplayRules
|
||||
// m_nShorthandedBonusLastEvalRound
|
||||
public ref Int32 ShorthandedBonusLastEvalRound => ref Schema.GetRef<Int32>(this.Handle, "CCSGameRules", "m_nShorthandedBonusLastEvalRound");
|
||||
|
||||
// m_bMatchAbortedDueToPlayerBan
|
||||
public ref bool MatchAbortedDueToPlayerBan => ref Schema.GetRef<bool>(this.Handle, "CCSGameRules", "m_bMatchAbortedDueToPlayerBan");
|
||||
// m_nMatchAbortedEarlyReason
|
||||
public ref Int32 MatchAbortedEarlyReason => ref Schema.GetRef<Int32>(this.Handle, "CCSGameRules", "m_nMatchAbortedEarlyReason");
|
||||
|
||||
// m_bHasTriggeredRoundStartMusic
|
||||
public ref bool HasTriggeredRoundStartMusic => ref Schema.GetRef<bool>(this.Handle, "CCSGameRules", "m_bHasTriggeredRoundStartMusic");
|
||||
@@ -7515,6 +7532,12 @@ public partial class CCSPlayer_MovementServices : CPlayer_MovementServices_Human
|
||||
// m_flStamina
|
||||
public ref float Stamina => ref Schema.GetRef<float>(this.Handle, "CCSPlayer_MovementServices", "m_flStamina");
|
||||
|
||||
// m_flHeightAtJumpStart
|
||||
public ref float HeightAtJumpStart => ref Schema.GetRef<float>(this.Handle, "CCSPlayer_MovementServices", "m_flHeightAtJumpStart");
|
||||
|
||||
// m_flMaxJumpHeightThisJump
|
||||
public ref float MaxJumpHeightThisJump => ref Schema.GetRef<float>(this.Handle, "CCSPlayer_MovementServices", "m_flMaxJumpHeightThisJump");
|
||||
|
||||
}
|
||||
|
||||
public partial class CCSPlayer_PingServices : CPlayerPawnComponent
|
||||
@@ -7792,6 +7815,9 @@ public partial class CCSPlayerController : CBasePlayerController
|
||||
// m_uiAbandonRecordedReason
|
||||
public ref UInt32 UiAbandonRecordedReason => ref Schema.GetRef<UInt32>(this.Handle, "CCSPlayerController", "m_uiAbandonRecordedReason");
|
||||
|
||||
// m_bCannotBeKicked
|
||||
public ref bool CannotBeKicked => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerController", "m_bCannotBeKicked");
|
||||
|
||||
// m_bEverFullyConnected
|
||||
public ref bool EverFullyConnected => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerController", "m_bEverFullyConnected");
|
||||
|
||||
@@ -7909,9 +7935,15 @@ public partial class CCSPlayerController : CBasePlayerController
|
||||
// m_bGaveTeamDamageWarningThisRound
|
||||
public ref bool GaveTeamDamageWarningThisRound => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerController", "m_bGaveTeamDamageWarningThisRound");
|
||||
|
||||
// m_dblLastReceivedPacketPlatFloatTime
|
||||
public ref double DblLastReceivedPacketPlatFloatTime => ref Schema.GetRef<double>(this.Handle, "CCSPlayerController", "m_dblLastReceivedPacketPlatFloatTime");
|
||||
|
||||
// m_LastTeamDamageWarningTime
|
||||
public ref float LastTeamDamageWarningTime => ref Schema.GetRef<float>(this.Handle, "CCSPlayerController", "m_LastTeamDamageWarningTime");
|
||||
|
||||
// m_LastTimePlayerWasDisconnectedForPawnsRemove
|
||||
public ref float LastTimePlayerWasDisconnectedForPawnsRemove => ref Schema.GetRef<float>(this.Handle, "CCSPlayerController", "m_LastTimePlayerWasDisconnectedForPawnsRemove");
|
||||
|
||||
}
|
||||
|
||||
public partial class CCSPlayerController_ActionTrackingServices : CPlayerControllerComponent
|
||||
@@ -8046,6 +8078,9 @@ public partial class CCSPlayerPawn : CCSPlayerPawnBase
|
||||
set { Schema.SetString(this.Handle, "CCSPlayerPawn", "m_szLastPlaceName", value); }
|
||||
}
|
||||
|
||||
// m_bInHostageResetZone
|
||||
public ref bool InHostageResetZone => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerPawn", "m_bInHostageResetZone");
|
||||
|
||||
// m_bInBuyZone
|
||||
public ref bool InBuyZone => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerPawn", "m_bInBuyZone");
|
||||
|
||||
@@ -9385,10 +9420,8 @@ public partial class CEntityIdentity : NativeObject
|
||||
|
||||
}
|
||||
|
||||
public partial class CEntityInstance : NativeObject
|
||||
public partial class CEntityInstance : NativeEntity
|
||||
{
|
||||
public CEntityInstance (IntPtr pointer) : base(pointer) {}
|
||||
|
||||
// m_iszPrivateVScripts
|
||||
public string PrivateVScripts
|
||||
{
|
||||
@@ -12825,6 +12858,9 @@ public partial class CHostage : CHostageExpresserShim
|
||||
// m_vecSpawnGroundPos
|
||||
public Vector SpawnGroundPos => Schema.GetDeclaredClass<Vector>(this.Handle, "CHostage", "m_vecSpawnGroundPos");
|
||||
|
||||
// m_vecHostageResetPosition
|
||||
public Vector HostageResetPosition => Schema.GetDeclaredClass<Vector>(this.Handle, "CHostage", "m_vecHostageResetPosition");
|
||||
|
||||
}
|
||||
|
||||
public partial class CHostageAlias_info_hostage_spawn : CHostage
|
||||
@@ -14153,6 +14189,28 @@ public partial class CLogicDistanceCheck : CLogicalEntity
|
||||
|
||||
}
|
||||
|
||||
public partial class CLogicEventListener : CLogicalEntity
|
||||
{
|
||||
public CLogicEventListener (IntPtr pointer) : base(pointer) {}
|
||||
|
||||
// m_strEventName
|
||||
public string StrEventName
|
||||
{
|
||||
get { return Schema.GetUtf8String(this.Handle, "CLogicEventListener", "m_strEventName"); }
|
||||
set { Schema.SetString(this.Handle, "CLogicEventListener", "m_strEventName", value); }
|
||||
}
|
||||
|
||||
// m_bIsEnabled
|
||||
public ref bool IsEnabled => ref Schema.GetRef<bool>(this.Handle, "CLogicEventListener", "m_bIsEnabled");
|
||||
|
||||
// m_nTeam
|
||||
public ref Int32 Team => ref Schema.GetRef<Int32>(this.Handle, "CLogicEventListener", "m_nTeam");
|
||||
|
||||
// m_OnEventFired
|
||||
public CEntityIOOutput OnEventFired => Schema.GetDeclaredClass<CEntityIOOutput>(this.Handle, "CLogicEventListener", "m_OnEventFired");
|
||||
|
||||
}
|
||||
|
||||
public partial class CLogicGameEvent : CLogicalEntity
|
||||
{
|
||||
public CLogicGameEvent (IntPtr pointer) : base(pointer) {}
|
||||
@@ -19746,6 +19804,63 @@ public partial class CTablet : CCSWeaponBase
|
||||
|
||||
}
|
||||
|
||||
public partial class CTakeDamageInfo : NativeObject
|
||||
{
|
||||
public CTakeDamageInfo (IntPtr pointer) : base(pointer) {}
|
||||
|
||||
// m_vecDamageForce
|
||||
public Vector DamageForce => Schema.GetDeclaredClass<Vector>(this.Handle, "CTakeDamageInfo", "m_vecDamageForce");
|
||||
|
||||
// m_vecDamagePosition
|
||||
public Vector DamagePosition => Schema.GetDeclaredClass<Vector>(this.Handle, "CTakeDamageInfo", "m_vecDamagePosition");
|
||||
|
||||
// m_vecReportedPosition
|
||||
public Vector ReportedPosition => Schema.GetDeclaredClass<Vector>(this.Handle, "CTakeDamageInfo", "m_vecReportedPosition");
|
||||
|
||||
// m_vecDamageDirection
|
||||
public Vector DamageDirection => Schema.GetDeclaredClass<Vector>(this.Handle, "CTakeDamageInfo", "m_vecDamageDirection");
|
||||
|
||||
// m_hInflictor
|
||||
public CHandle<CBaseEntity> Inflictor => Schema.GetDeclaredClass<CHandle<CBaseEntity>>(this.Handle, "CTakeDamageInfo", "m_hInflictor");
|
||||
|
||||
// m_hAttacker
|
||||
public CHandle<CBaseEntity> Attacker => Schema.GetDeclaredClass<CHandle<CBaseEntity>>(this.Handle, "CTakeDamageInfo", "m_hAttacker");
|
||||
|
||||
// m_hAbility
|
||||
public CHandle<CBaseEntity> Ability => Schema.GetDeclaredClass<CHandle<CBaseEntity>>(this.Handle, "CTakeDamageInfo", "m_hAbility");
|
||||
|
||||
// m_flDamage
|
||||
public ref float Damage => ref Schema.GetRef<float>(this.Handle, "CTakeDamageInfo", "m_flDamage");
|
||||
|
||||
// m_bitsDamageType
|
||||
public ref Int32 BitsDamageType => ref Schema.GetRef<Int32>(this.Handle, "CTakeDamageInfo", "m_bitsDamageType");
|
||||
|
||||
// m_iDamageCustom
|
||||
public ref Int32 DamageCustom => ref Schema.GetRef<Int32>(this.Handle, "CTakeDamageInfo", "m_iDamageCustom");
|
||||
|
||||
// m_iAmmoType
|
||||
public ref byte AmmoType => ref Schema.GetRef<byte>(this.Handle, "CTakeDamageInfo", "m_iAmmoType");
|
||||
|
||||
// m_flOriginalDamage
|
||||
public ref float OriginalDamage => ref Schema.GetRef<float>(this.Handle, "CTakeDamageInfo", "m_flOriginalDamage");
|
||||
|
||||
// m_bShouldBleed
|
||||
public ref bool ShouldBleed => ref Schema.GetRef<bool>(this.Handle, "CTakeDamageInfo", "m_bShouldBleed");
|
||||
|
||||
// m_bShouldSpark
|
||||
public ref bool ShouldSpark => ref Schema.GetRef<bool>(this.Handle, "CTakeDamageInfo", "m_bShouldSpark");
|
||||
|
||||
// m_nDamageFlags
|
||||
public ref TakeDamageFlags_t DamageFlags => ref Schema.GetRef<TakeDamageFlags_t>(this.Handle, "CTakeDamageInfo", "m_nDamageFlags");
|
||||
|
||||
// m_nNumObjectsPenetrated
|
||||
public ref Int32 NumObjectsPenetrated => ref Schema.GetRef<Int32>(this.Handle, "CTakeDamageInfo", "m_nNumObjectsPenetrated");
|
||||
|
||||
// m_bInTakeDamageFlow
|
||||
public ref bool InTakeDamageFlow => ref Schema.GetRef<bool>(this.Handle, "CTakeDamageInfo", "m_bInTakeDamageFlow");
|
||||
|
||||
}
|
||||
|
||||
public partial class CTankTargetChange : CPointEntity
|
||||
{
|
||||
public CTankTargetChange (IntPtr pointer) : base(pointer) {}
|
||||
@@ -20138,6 +20253,12 @@ public partial class CTriggerGravity : CBaseTrigger
|
||||
|
||||
}
|
||||
|
||||
public partial class CTriggerHostageReset : CBaseTrigger
|
||||
{
|
||||
public CTriggerHostageReset (IntPtr pointer) : base(pointer) {}
|
||||
|
||||
}
|
||||
|
||||
public partial class CTriggerHurt : CBaseTrigger
|
||||
{
|
||||
public CTriggerHurt (IntPtr pointer) : base(pointer) {}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
|
||||
public interface IPluginContextQueryHandler
|
||||
{
|
||||
IPluginContext? FindPluginByType(Type moduleClass);
|
||||
|
||||
IPluginContext? FindPluginById(int id);
|
||||
|
||||
IPluginContext? FindPluginByModuleName(string name);
|
||||
|
||||
IPluginContext? FindPluginByModulePath(string path);
|
||||
|
||||
IPluginContext? FindPluginByIdOrName(string query);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
|
||||
public interface IPluginManager
|
||||
{
|
||||
public void Load();
|
||||
public void LoadPlugin(string path);
|
||||
public IEnumerable<PluginContext> GetLoadedPlugins();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
|
||||
public class PluginContextQueryHandler : IPluginContextQueryHandler
|
||||
{
|
||||
private readonly IPluginManager _pluginManager;
|
||||
|
||||
public PluginContextQueryHandler(IPluginManager pluginManager)
|
||||
{
|
||||
_pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
public IPluginContext? FindPluginByType(Type moduleClass)
|
||||
{
|
||||
return _pluginManager.GetLoadedPlugins().FirstOrDefault(x => x.Plugin.GetType() == moduleClass);
|
||||
}
|
||||
|
||||
public IPluginContext? FindPluginById(int id)
|
||||
{
|
||||
return _pluginManager.GetLoadedPlugins().FirstOrDefault(x => x.PluginId == id);
|
||||
}
|
||||
|
||||
public IPluginContext? FindPluginByModuleName(string name)
|
||||
{
|
||||
return _pluginManager.GetLoadedPlugins().FirstOrDefault(x => x.Plugin.ModuleName == name);
|
||||
}
|
||||
|
||||
public IPluginContext? FindPluginByModulePath(string path)
|
||||
{
|
||||
return _pluginManager.GetLoadedPlugins().FirstOrDefault(x => x.Plugin.ModulePath == path);
|
||||
}
|
||||
|
||||
public IPluginContext? FindPluginByIdOrName(string query)
|
||||
{
|
||||
return _pluginManager.GetLoadedPlugins().FirstOrDefault(x => x.PluginId.ToString() == query || x.Plugin.ModuleName == query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
|
||||
public class PluginManager : IPluginManager
|
||||
{
|
||||
private readonly HashSet<PluginContext> _loadedPluginContexts = new();
|
||||
private readonly IScriptHostConfiguration _scriptHostConfiguration;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger<PluginManager> _logger;
|
||||
|
||||
public PluginManager(IScriptHostConfiguration scriptHostConfiguration, ILogger<PluginManager> logger, IServiceProvider serviceProvider)
|
||||
{
|
||||
_scriptHostConfiguration = scriptHostConfiguration;
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
var pluginDirectories = Directory.GetDirectories(_scriptHostConfiguration.PluginPath);
|
||||
var pluginAssemblyPaths = pluginDirectories
|
||||
.Select(dir => Path.Combine(dir, Path.GetFileName(dir) + ".dll"))
|
||||
.Where(File.Exists)
|
||||
.ToArray();
|
||||
|
||||
foreach (var path in pluginAssemblyPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadPlugin(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed to load plugin from {Path}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<PluginContext> GetLoadedPlugins()
|
||||
{
|
||||
return _loadedPluginContexts;
|
||||
}
|
||||
|
||||
public void LoadPlugin(string path)
|
||||
{
|
||||
var plugin = new PluginContext(_serviceProvider, _scriptHostConfiguration, path, _loadedPluginContexts.Select(x => x.PluginId).DefaultIfEmpty(0).Max() + 1);
|
||||
_loadedPluginContexts.Add(plugin);
|
||||
plugin.Load();
|
||||
}
|
||||
}
|
||||
11
managed/CounterStrikeSharp.API/Core/Plugin/IPluginContext.cs
Normal file
11
managed/CounterStrikeSharp.API/Core/Plugin/IPluginContext.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace CounterStrikeSharp.API.Core.Plugin;
|
||||
|
||||
public interface IPluginContext
|
||||
{
|
||||
PluginState State { get; }
|
||||
IPlugin Plugin { get; }
|
||||
int PluginId { get; }
|
||||
|
||||
void Load(bool hotReload);
|
||||
void Unload(bool hotReload);
|
||||
}
|
||||
215
managed/CounterStrikeSharp.API/Core/Plugin/PluginContext.cs
Normal file
215
managed/CounterStrikeSharp.API/Core/Plugin/PluginContext.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using CounterStrikeSharp.API.Core.Logging;
|
||||
using McMaster.NETCore.Plugins;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Plugin
|
||||
{
|
||||
public class PluginContext : IPluginContext
|
||||
{
|
||||
public PluginState State { get; set; } = PluginState.Unregistered;
|
||||
public IPlugin Plugin { get; private set; }
|
||||
|
||||
private PluginLoader Loader { get; set; }
|
||||
|
||||
private IServiceProvider ServiceProvider { get; set; }
|
||||
|
||||
public int PluginId { get; }
|
||||
|
||||
private readonly IScriptHostConfiguration _hostConfiguration;
|
||||
private readonly string _path;
|
||||
private readonly FileSystemWatcher _fileWatcher;
|
||||
private readonly IServiceProvider _applicationServiceProvider;
|
||||
|
||||
// TOOD: ServiceCollection
|
||||
private ILogger _logger = CoreLogging.Factory.CreateLogger<PluginContext>();
|
||||
|
||||
public PluginContext(IServiceProvider applicationServiceProvider, IScriptHostConfiguration hostConfiguration, string path, int id)
|
||||
{
|
||||
_applicationServiceProvider = applicationServiceProvider;
|
||||
_hostConfiguration = hostConfiguration;
|
||||
_path = path;
|
||||
PluginId = id;
|
||||
|
||||
Loader = PluginLoader.CreateFromAssemblyFile(path,
|
||||
new[]
|
||||
{
|
||||
typeof(IPlugin), typeof(ILogger), typeof(IServiceCollection), typeof(IPluginServiceCollection<>)
|
||||
}, config =>
|
||||
{
|
||||
config.EnableHotReload = true;
|
||||
config.IsUnloadable = true;
|
||||
});
|
||||
|
||||
if (CoreConfig.PluginHotReloadEnabled)
|
||||
{
|
||||
_fileWatcher = new FileSystemWatcher
|
||||
{
|
||||
Path = Path.GetDirectoryName(path)
|
||||
};
|
||||
|
||||
_fileWatcher.Deleted += async (s, e) =>
|
||||
{
|
||||
Server.NextWorldUpdate(() =>
|
||||
{
|
||||
if (e.FullPath == path)
|
||||
{
|
||||
_logger.LogInformation("Plugin {Name} has been deleted, unloading...", Plugin.ModuleName);
|
||||
Unload(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
_fileWatcher.Filter = "*.dll";
|
||||
_fileWatcher.EnableRaisingEvents = true;
|
||||
Loader.Reloaded += async (s, e) => await OnReloadedAsync(s, e);
|
||||
}
|
||||
}
|
||||
|
||||
private Task OnReloadedAsync(object sender, PluginReloadedEventArgs eventargs)
|
||||
{
|
||||
Server.NextWorldUpdate(() =>
|
||||
{
|
||||
_logger.LogInformation("Reloading plugin {Name}", Plugin.ModuleName);
|
||||
Loader = eventargs.Loader;
|
||||
Unload(hotReload: true);
|
||||
Load(hotReload: true);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Load(bool hotReload = false)
|
||||
{
|
||||
if (State == PluginState.Loaded) return;
|
||||
|
||||
using (Loader.EnterContextualReflection())
|
||||
{
|
||||
var defaultAssembly = Loader.LoadDefaultAssembly();
|
||||
|
||||
Type pluginType = defaultAssembly.GetTypes()
|
||||
.FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));
|
||||
|
||||
if (pluginType == null) throw new Exception("Unable to find plugin in assembly");
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
|
||||
serviceCollection.Scan(scan =>
|
||||
scan.FromAssemblies(defaultAssembly)
|
||||
.AddClasses(c => c.AssignableTo<IPlugin>())
|
||||
.AsSelf()
|
||||
.WithSingletonLifetime()
|
||||
);
|
||||
|
||||
serviceCollection.AddLogging(builder =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
builder.AddSerilog(new LoggerConfiguration()
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.With(new PluginNameEnricher(this))
|
||||
.WriteTo.Console(
|
||||
outputTemplate:
|
||||
"{Timestamp:HH:mm:ss} [{Level:u4}] (plugin:{PluginName}) {Message:lj}{NewLine}{Exception}")
|
||||
.WriteTo.File(
|
||||
Path.Join(new[]
|
||||
{
|
||||
_hostConfiguration.RootPath, "logs",
|
||||
$"log-{pluginType.Assembly.GetName().Name}.txt"
|
||||
}), rollingInterval: RollingInterval.Day,
|
||||
outputTemplate:
|
||||
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] plugin:{PluginName} {Message:lj}{NewLine}{Exception}")
|
||||
.WriteTo.File(Path.Join(new[] { _hostConfiguration.RootPath, "logs", $"log-all.txt" }),
|
||||
rollingInterval: RollingInterval.Day, shared: true,
|
||||
outputTemplate:
|
||||
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] plugin:{PluginName} {Message:lj}{NewLine}{Exception}")
|
||||
.CreateLogger());
|
||||
});
|
||||
|
||||
Type interfaceType = typeof(IPluginServiceCollection<>).MakeGenericType(pluginType);
|
||||
Type[] serviceCollectionConfiguratorTypes = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(assembly => assembly.GetTypes())
|
||||
.Where(type => interfaceType.IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
|
||||
.ToArray();
|
||||
|
||||
if (serviceCollectionConfiguratorTypes.Any())
|
||||
{
|
||||
foreach (var t in serviceCollectionConfiguratorTypes)
|
||||
{
|
||||
var pluginServiceCollection = Activator.CreateInstance(t);
|
||||
MethodInfo method = t.GetMethod("ConfigureServices");
|
||||
method?.Invoke(pluginServiceCollection, new object[] { serviceCollection });
|
||||
}
|
||||
}
|
||||
|
||||
serviceCollection.AddSingleton(this);
|
||||
ServiceProvider = serviceCollection.BuildServiceProvider();
|
||||
|
||||
var minimumApiVersion = pluginType.GetCustomAttribute<MinimumApiVersion>()?.Version;
|
||||
var currentVersion = Api.GetVersion();
|
||||
|
||||
// Ignore version 0 for local development
|
||||
if (currentVersion > 0 && minimumApiVersion != null && minimumApiVersion > currentVersion)
|
||||
throw new Exception(
|
||||
$"Plugin \"{Path.GetFileName(_path)}\" requires a newer version of CounterStrikeSharp. The plugin expects version [{minimumApiVersion}] but the current version is [{currentVersion}].");
|
||||
|
||||
_logger.LogInformation("Loading plugin {Name}", pluginType.Assembly.GetName().Name);
|
||||
|
||||
Plugin = ServiceProvider.GetRequiredService(pluginType) as IPlugin;
|
||||
|
||||
if (Plugin == null) throw new Exception("Unable to create plugin instance");
|
||||
|
||||
State = PluginState.Loading;
|
||||
|
||||
Plugin.ModulePath = _path;
|
||||
Plugin.RegisterAllAttributes(Plugin);
|
||||
Plugin.Logger = ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(pluginType);
|
||||
|
||||
Plugin.InitializeConfig(Plugin, pluginType);
|
||||
Plugin.Load(hotReload);
|
||||
|
||||
_logger.LogInformation("Finished loading plugin {Name}", Plugin.ModuleName);
|
||||
|
||||
State = PluginState.Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
public void Unload(bool hotReload = false)
|
||||
{
|
||||
if (State == PluginState.Unloaded) return;
|
||||
|
||||
State = PluginState.Unloaded;
|
||||
var cachedName = Plugin.ModuleName;
|
||||
|
||||
_logger.LogInformation("Unloading plugin {Name}", Plugin.ModuleName);
|
||||
|
||||
Plugin.Unload(hotReload);
|
||||
|
||||
Plugin.Dispose();
|
||||
|
||||
_logger.LogInformation("Finished unloading plugin {Name}", cachedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace CounterStrikeSharp.API.Core.Plugin;
|
||||
|
||||
public enum PluginState
|
||||
{
|
||||
Unregistered,
|
||||
Loading,
|
||||
Loaded,
|
||||
Unloaded,
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Modules.Events;
|
||||
using McMaster.NETCore.Plugins;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
public class PluginContext
|
||||
{
|
||||
private BasePlugin _plugin;
|
||||
private PluginLoader _assemblyLoader;
|
||||
|
||||
public string Name => _plugin?.ModuleName;
|
||||
public string Version => _plugin?.ModuleVersion;
|
||||
public string Description => _plugin.ModuleDescription;
|
||||
public string Author => _plugin.ModuleAuthor;
|
||||
public Type PluginType => _plugin?.GetType();
|
||||
public string PluginPath => _plugin?.ModulePath;
|
||||
|
||||
public int PluginId { get; }
|
||||
|
||||
private readonly string _path;
|
||||
private readonly FileSystemWatcher _fileWatcher;
|
||||
|
||||
public PluginContext(string path, int id)
|
||||
{
|
||||
_path = path;
|
||||
PluginId = id;
|
||||
|
||||
_assemblyLoader = PluginLoader.CreateFromAssemblyFile(path, new[] { typeof(IPlugin) }, config =>
|
||||
{
|
||||
config.EnableHotReload = true;
|
||||
config.IsUnloadable = true;
|
||||
});
|
||||
|
||||
_fileWatcher = new FileSystemWatcher
|
||||
{
|
||||
Path = Path.GetDirectoryName(path)
|
||||
};
|
||||
|
||||
_fileWatcher.Deleted += async (s, e) =>
|
||||
{
|
||||
if (e.FullPath == path)
|
||||
{
|
||||
Console.WriteLine($"Plugin {Name} has been deleted, unloading...");
|
||||
Unload(true);
|
||||
}
|
||||
};
|
||||
|
||||
_fileWatcher.Filter = "*.dll";
|
||||
_fileWatcher.EnableRaisingEvents = true;
|
||||
_assemblyLoader.Reloaded += async (s, e) => await OnReloadedAsync(s, e);
|
||||
}
|
||||
|
||||
private Task OnReloadedAsync(object sender, PluginReloadedEventArgs eventargs)
|
||||
{
|
||||
Console.WriteLine($"Reloading plugin {Name}");
|
||||
_assemblyLoader = eventargs.Loader;
|
||||
Unload(hotReload: true);
|
||||
Load(hotReload: true);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Load(bool hotReload = false)
|
||||
{
|
||||
using (_assemblyLoader.EnterContextualReflection())
|
||||
{
|
||||
Type pluginType = _assemblyLoader.LoadDefaultAssembly().GetTypes()
|
||||
.FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));
|
||||
|
||||
if (pluginType == null) throw new Exception("Unable to find plugin in DLL");
|
||||
|
||||
var minimumApiVersion = pluginType.GetCustomAttribute<MinimumApiVersion>()?.Version;
|
||||
var currentVersion = Api.GetVersion();
|
||||
|
||||
// Ignore version 0 for local development
|
||||
if (currentVersion > 0 && minimumApiVersion != null && minimumApiVersion > currentVersion)
|
||||
throw new Exception(
|
||||
$"Plugin \"{Path.GetFileName(_path)}\" requires a newer version of CounterStrikeSharp. The plugin expects version [{minimumApiVersion}] but the current version is [{currentVersion}].");
|
||||
|
||||
Console.WriteLine($"Loading plugin: {pluginType.Name}");
|
||||
_plugin = (BasePlugin)Activator.CreateInstance(pluginType)!;
|
||||
_plugin.ModulePath = _path;
|
||||
_plugin.RegisterAllAttributes(_plugin);
|
||||
_plugin.Load(hotReload);
|
||||
|
||||
Console.WriteLine($"Finished loading plugin: {Name}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Unload(bool hotReload = false)
|
||||
{
|
||||
var cachedName = Name;
|
||||
|
||||
Console.WriteLine($"Unloading plugin {Name}");
|
||||
|
||||
_plugin.Unload(hotReload);
|
||||
|
||||
_plugin.Dispose();
|
||||
|
||||
if (!hotReload)
|
||||
{
|
||||
_assemblyLoader.Dispose();
|
||||
_fileWatcher.Dispose();
|
||||
}
|
||||
|
||||
Console.WriteLine($"Finished unloading plugin {cachedName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,13 +67,11 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
public unsafe ScriptContext()
|
||||
{
|
||||
//Console.WriteLine("Global context address: " + (IntPtr)m_extContext);
|
||||
}
|
||||
|
||||
public unsafe ScriptContext(fxScriptContext* context)
|
||||
{
|
||||
m_extContext = *context;
|
||||
//Console.WriteLine("Global context address: " + (IntPtr)m_extContext);
|
||||
}
|
||||
|
||||
private readonly ConcurrentQueue<Action> ms_finalizers = new ConcurrentQueue<Action>();
|
||||
@@ -207,6 +205,16 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
return;
|
||||
}
|
||||
else if (arg is NativeObject nativeObject)
|
||||
{
|
||||
Push(context, (InputArgument)nativeObject);
|
||||
return;
|
||||
}
|
||||
else if (arg is NativeEntity nativeValue)
|
||||
{
|
||||
Push(context, (InputArgument)nativeValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Marshal.SizeOf(arg.GetType()) <= 8)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user