Compare commits

..

9 Commits

Author SHA1 Message Date
roflmuffin
49974d23fc Revert "Merge remote-tracking branch 'origin/main' into main"
This reverts commit 89ef4a6323, reversing
changes made to 5211fde474.
2023-12-05 14:50:49 +10:00
roflmuffin
89ef4a6323 Merge remote-tracking branch 'origin/main' into main 2023-12-05 14:49:04 +10:00
Poggu
5211fde474 add natives into codegen 2023-12-03 22:02:38 +01:00
Poggu
291c0db25e fix equality check 2023-12-03 09:57:25 +01:00
Poggu
0f994b4ae1 Merge pull request #1 from KillStr3aK/main
Implementation of `HookSingleEntityOutput` and `UnhookSingleEntityOutput`
2023-12-02 23:13:58 +01:00
KillStr3aK
64ddf3ef19 clear single entity hooks on map end 2023-12-02 23:09:18 +01:00
KillStr3aK
9b11af112c implemented HookSingleEntityOutput and UnhookSingleEntityOutput 2023-12-02 22:53:29 +01:00
Poggu
1b6bd22e06 add entity output attribute 2023-12-02 18:38:51 +01:00
Poggu
8cf9024ee5 add entity output hooking 2023-12-02 17:09:40 +01:00
106 changed files with 4582 additions and 1579 deletions

View File

@@ -3,7 +3,7 @@ name: Build & Publish
on:
push:
paths-ignore:
- 'docfx/**'
- 'docs/**'
branches: [ "main" ]
pull_request:
branches: [ "main" ]

View File

@@ -1,46 +1,42 @@
on:
push:
branches:
- main
name: Deploy to GitHub Pages
on:
# Trigger the workflow every time you push to the `main` branch
# Using a different branch name? Replace `main` with your branchs name
push:
paths:
- 'docs/**'
branches: [ main ]
# Allows you to run this workflow manually from the Actions tab on GitHub.
workflow_dispatch:
# Allow this job to clone the repo and create a page deployment
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
publish-docs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout your repository using git
uses: actions/checkout@v3
- name: Install, build, and upload your site
uses: withastro/action@v1
with:
path: ./docs # The root location of your Astro project inside the repository. (optional)
node-version: 20 # The specific version of Node that should be used to build your site. Defaults to 18. (optional)
package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional)
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Dotnet Setup
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.x
- run: dotnet tool update -g docfx
- run: docfx docfx/docfx.json
- name: Setup Pages
uses: actions/configure-pages@v3
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
with:
path: "docfx/_site"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
uses: actions/deploy-pages@v1

9
.gitignore vendored
View File

@@ -1,6 +1,6 @@
.ccls-cache/
.cmake/
cmake-build-debug*/
cmake-build-debug/
.kdev4/
.vscode/
generated/
@@ -548,9 +548,4 @@ $RECYCLE.BIN/
_NCrunch*
.idea/
# docfx
docfx/_site/
docfx/api/
docfx/_exported_templates/
.idea/

View File

@@ -1,3 +0,0 @@
# 404
We recently changed our docs. Your page may exist at a different location.

View File

@@ -1,46 +0,0 @@
{
"metadata": [
{
"src": [
{
"src": "../managed/CounterStrikeSharp.API",
"files": ["**/*.csproj"],
"exclude": ["**/bin/**", "**/obj/**"]
}
],
"filter": "filterConfig.yml",
"dest": "api",
"namespaceLayout": "nested"
}
],
"build": {
"content": [
{
"files": ["**/*.{md,yml}"],
"exclude": ["_site/**"]
}
],
"resource": [
{
"files": ["images/**"]
}
],
"output": "_site",
"template": ["default", "modern", "layouts/cssharp"],
"globalMetadata": {
"_appFaviconPath": "images/cssharp.svg",
"_appFooter": "<a href=\"https://docs.cssharp.dev\">docs.cssharp.dev</a>",
"_appLogoPath": "images/cssharp.svg",
"_appName": "CounterStrikeSharp",
"_appTitle": "CounterStrikeSharp",
"_disableNewTab": true,
"_enableNewTab": true,
"_enableSearch": true,
"pdf": false
},
"sitemap": {
"baseUrl": "https://docs.cssharp.dev",
"changefreq": "hourly"
}
}
}

View File

@@ -1,14 +0,0 @@
- name: Admin Command Attributes
href: admin-command-attributes.md
- name: Defining Admins
href: defining-admins.md
- name: Defining Command Overrides
href: defining-command-overrides.md
- name: Defining Admin Groups
href: defining-admin-groups.md
- name: Defining Admin Immunity
href: defining-admin-immunity.md

View File

@@ -1,11 +0,0 @@
- name: Console Commands
href: console-commands.md
- name: Console Variables
href: console-variables.md
- name: Game Events
href: game-events.md
- name: Global Listeners
href: global-listeners.md

View File

@@ -1,63 +0,0 @@
---
title: Getting Started
description: How to get started installing & using CounterStrikeSharp.
---
# Getting Started
How to get started installing & using CounterStrikeSharp.
## Installing Metamod
`CounterStrikeSharp` uses `Metamod:Source` as its main way of communicating with the game server. To install it, you can follow the detailed instructions found <a href="https://cs2.poggu.me/metamod/installation/" target="_blank">here</a>.
## Installing CounterStrikeSharp
Download the latest release of CounterStrikeSharp from <a href="https://github.com/roflmuffin/CounterStrikeSharp/actions/workflows/cmake-single-platform.yml" target="_blank">GitHub actions build pages</a> (just choose the latest development snapshot). **You may need to be logged into GitHub to gain access to the downloads**.
> [!CAUTION]
> If this is your first time installing, you will need to download the `with-runtime` version. This includes a copy of the .NET runtime, which is required to run the plugin.
> Depending on the os you might also either need to install `libicu` / `icu-libs` / `libicu-dev` using your package manager for .NET to run or setting `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true` in your servers environment variables. You can find more infos about that <a href="https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#enabling-the-invariant-mode" target="_blank">here</a>
>
> Subsequent upgrades will not require the runtime, unless a version bump of the .NET runtime is required (i.e. from 7.0.x to 8.0.x). We will inform you when this occurs.
> [!CAUTION]
> For Windows users, you must ensure that you have installed **Visual Studio Redistributables**.
> If not, you can download it here: <a href="https://aka.ms/vs/17/release/vc_redist.x64.exe" target="_blank">Download</a>
> > This link will download VC Redistributable directly.
>
> You must install it before starting the server, otherwise CSS will not work!
Extract the `addons` folder to the `/csgo/` directory of the dedicated server. The contents of your addons folder should contain both the `counterstrikesharp` folder and the `metamod` folder as seen below.
```shell
<server_path>/game/csgo/addons > tree -L 2
addons
├── counterstrikesharp
│ ├── api
│ ├── bin
│ ├── dotnet
│ ├── plugins
│ └── gamedata
├── metamod
│ ├── bin
│ ├── counterstrikesharp.vdf
│ ├── metaplugins.ini
│ └── README.txt
├── metamod.vdf
└── metamod_x64.vdf
```
## Start the Server
Launch your CS2 dedicated server as normal. If everything is working correctly, you should see a message in the console that says `CSSharp: CounterStrikeSharp.API Loaded Successfully.`.
Running the command `meta list` in the console should show 1 plugin loaded 🎉
```shell
meta list
Listing 1 plugin:
[01] CounterStrikeSharp (0.1.0) by Roflmuffin
```

View File

@@ -1,8 +0,0 @@
- name: Getting Started
href: getting-started.md
- name: Hello World Plugin
href: hello-world-plugin.md
- name: Dependency Injection
href: dependency-injection.md

View File

@@ -1,5 +0,0 @@
- name: Core Configuration
href: core-configuration.md
- name: Referencing Players
href: referencing-players.md

View File

@@ -1,16 +0,0 @@
items:
- name: Guides
href: guides/toc.yml
expanded: true
- name: Features
href: features/toc.yml
expanded: true
- name: Admin Framework
href: admin-framework/toc.yml
expanded: true
- name: Reference
href: reference/toc.yml
expanded: true

View File

@@ -1,5 +0,0 @@
[!INCLUDE [HelloWorld](../../examples/HelloWorld/README.md)]
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/HelloWorld" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
[!code-csharp[](../../examples/HelloWorld/HelloWorldPlugin.cs)]

View File

@@ -1,13 +0,0 @@
# Warcraft Plugin
This Warcraft plugin is migrated from the previous VSP.NET project to give you an idea of the kind of power this scripting runtime is capable of.
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WarcraftPlugin" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
## Demonstrated
- Hook events
- Create commands
- Use third party libraries
- SQLite
- Entity manipulation

View File

@@ -1,5 +0,0 @@
[!INCLUDE [WithCommands](../../examples/WithCommands/README.md)]
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WithCommands" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
[!code-csharp[](../../examples/WithCommands/WithCommandsPlugin.cs)]

View File

@@ -1,5 +0,0 @@
[!INCLUDE [WithConfig](../../examples/WithConfig/README.md)]
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WithConfig" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
[!code-csharp[](../../examples/WithConfig/WithConfigPlugin.cs)]

View File

@@ -1,5 +0,0 @@
[!INCLUDE [Database](../../examples/WithDatabaseDapper/README.md)]
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WithDatabaseDapper" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
[!code-csharp[](../../examples/WithDatabaseDapper/WithDatabaseDapperPlugin.cs)]

View File

@@ -1,5 +0,0 @@
[!INCLUDE [WithDependencyInjection](../../examples/WithDependencyInjection/README.md)]
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WithDependencyInjection" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
[!code-csharp[](../../examples/WithDependencyInjection/WithDependencyInjectionPlugin.cs)]

View File

@@ -1,5 +0,0 @@
[!INCLUDE [WithEntityOutputHooks](../../examples/WithEntityOutputHooks/README.md)]
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WithEntityOutputHooks" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
[!code-csharp[](../../examples/WithEntityOutputHooks/WithEntityOutputHooksPlugin.cs)]

View File

@@ -1,5 +0,0 @@
[!INCLUDE [WithGameEventHandlers](../../examples/WithGameEventHandlers/README.md)]
<a href="https://github.com/roflmuffin/CounterStrikeSharp/tree/main/examples/WithGameEventHandlers" class="btn btn-secondary">View project on Github <i class="bi bi-github"></i></a>
[!code-csharp[](../../examples/WithGameEventHandlers/WithGameEventHandlersPlugin.cs)]

View File

@@ -1,17 +0,0 @@
items:
- name: Hello World
href: HelloWorld.md
- name: Commands
href: WithCommands.md
- name: Config
href: WithConfig.md
- name: Dependency Injection
href: WithDependencyInjection.md
- name: Entity Output Hooks
href: WithEntityOutputHooks.md
- name: Game Event Handlers
href: WithGameEventHandlers.md
- name: Database (Dapper)
href: WithDatabase.md
- name: Warcraft Plugin
href: WarcraftPlugin.md

View File

@@ -1,4 +0,0 @@
apiRules:
- exclude:
uidRegex: ^System\.Object
type: Type

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" fill="none"><path fill="#F89A0F" d="m37.456 1.262-19.008.148a91.03 91.03 0 0 0-5.96.215A26.294 26.294 0 0 0 1.523 4.793C.643 5.26 0 6.07 0 7.06a2.464 2.464 0 0 0 3.704 2.128l.015-.007a20.538 20.538 0 0 1 7.93-2.495c8.024-.858 13.602 2.018 17.367 5.397 4.138 3.712 7.967 11.271 6.576 19.56-.831 4.962-3.122 9.146-6.69 12.397l-.007.007c-.646.59-1.18 1.274-1.18 2.147a2.547 2.547 0 0 0 4.158 1.965c.102-.075.22-.185.276-.238a27.692 27.692 0 0 0 6.652-9.698l10.126-24.027c.476-1.085.877-2.203 1.001-3.292.537-4.762-2.007-7.502-5.227-8.769-1.648-.65-3.968-.846-6.5-.869l-.745-.004Zm2.74 3.032c1.799.075 3.553 1.372 3.326 3.741-.276 2.865-3.137 4.865-4.384 6.747h4.384c.264.385.264 1.637 0 2.022-2.204-.215-4.66-.178-7.083-.17-1.036-3.783 5.367-6.327 4.72-9.441-.306-1.47-2.403-1.512-2.025.676h-2.529c-.045-2.506 1.796-3.651 3.59-3.575Z"/><path fill="#A079DF" d="M28.422 21.551a2.807 2.807 0 0 0-.34-1.413 2.7 2.7 0 0 0-1.028-.998c-3.772-2.174-7.54-4.34-11.304-6.516a2.774 2.774 0 0 0-3.01.03c-1.5.888-9.01 5.186-11.247 6.486A2.616 2.616 0 0 0 .12 21.55V34.66c0 .525.113.982.333 1.387a2.6 2.6 0 0 0 1.039 1.02c2.237 1.3 9.748 5.602 11.248 6.49a2.778 2.778 0 0 0 3.008.026c3.765-2.173 7.54-4.339 11.309-6.512.434-.24.794-.594 1.04-1.024.226-.427.339-.904.328-1.387l-.004-13.108Z"/><path fill="#270068" d="M14.317 28.06.454 36.05a2.6 2.6 0 0 0 1.039 1.02c2.237 1.3 9.748 5.601 11.248 6.49a2.778 2.778 0 0 0 3.008.026c3.765-2.173 7.54-4.339 11.309-6.512.434-.24.794-.594 1.04-1.024l-13.781-7.99Z"/><path fill="#390091" d="M28.422 21.551a2.807 2.807 0 0 0-.34-1.413l-13.769 7.925 13.78 7.983c.227-.427.34-.904.33-1.387V21.55Z"/><path fill="#fff" d="M22.48 25.078v1.492h1.494v-1.492h.744v1.492h1.493v.745h-1.493v1.493h1.493v.748h-1.493v1.49h-.744v-1.49H22.48v1.49h-.748v-1.49H20.24v-.748h1.493v-1.493H20.24v-.745h1.493v-1.492h.748Zm1.494 2.237H22.48v1.493h1.493v-1.493Z"/><path fill="#fff" d="M14.347 17.583c3.893 0 7.295 2.116 9.109 5.253l-.015-.026-4.58 2.638a5.236 5.236 0 0 0-4.453-2.582h-.06a5.238 5.238 0 1 0 4.565 7.809l-.023.038 4.574 2.645a10.519 10.519 0 0 1-8.996 5.27h-.12a10.522 10.522 0 1 1 0-21.045Z"/></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,23 +0,0 @@
---
_layout: landing
uid: home
title: CounterStrikeSharp
_appTitle: ''
description: Write Counter-Strike 2 server plugins in C#.
---
<div class="row justify-content-md-center">
<div class="col-12 col-lg-10 col-xl-8 col-xxl-6">
<div class="text-center">
<img src="images/cssharp.svg" height="128" width="128">
<h1 class="h1">CounterStrikeSharp</h1>
<span>CounterStrikeSharp is a simpler way to write CS2 server plugins.</span>
<div>
<a href="docs/guides/getting-started.md" class="btn btn-primary btn-lg fw-bold my-5">Get Started <i class="bi bi-arrow-right"></a>
</div>
</div>
[!code-csharp[](../examples/HelloWorld/HelloWorldPlugin.cs)]
</div>
</div>

View File

@@ -1,152 +0,0 @@
{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
{{!include(/^public/.*/)}}
{{!include(favicon.ico)}}
{{!include(logo.svg)}}
<!DOCTYPE html>
<html {{#_lang}}lang="{{_lang}}"{{/_lang}}>
<head>
<meta charset="utf-8">
{{#redirect_url}}
<meta http-equiv="refresh" content="0;URL='{{redirect_url}}'">
{{/redirect_url}}
{{^redirect_url}}
<title>{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}">
{{#_description}}<meta name="description" content="{{_description}}">{{/_description}}
<link rel="icon" href="{{_rel}}{{{_appFaviconPath}}}{{^_appFaviconPath}}favicon.ico{{/_appFaviconPath}}">
<link rel="stylesheet" href="{{_rel}}public/docfx.min.css">
<link rel="stylesheet" href="{{_rel}}public/main.css">
<meta name="docfx:navrel" content="{{_navRel}}">
<meta name="docfx:tocrel" content="{{_tocRel}}">
{{#_noindex}}<meta name="searchOption" content="noindex">{{/_noindex}}
{{#_enableSearch}}<meta name="docfx:rel" content="{{_rel}}">{{/_enableSearch}}
{{#_disableNewTab}}<meta name="docfx:disablenewtab" content="true">{{/_disableNewTab}}
{{#_disableTocFilter}}<meta name="docfx:disabletocfilter" content="true">{{/_disableTocFilter}}
{{#docurl}}<meta name="docfx:docurl" content="{{docurl}}">{{/docurl}}
<meta name="loc:inThisArticle" content="{{__global.inThisArticle}}">
<meta name="loc:searchResultsCount" content="{{__global.searchResultsCount}}">
<meta name="loc:searchNoResults" content="{{__global.searchNoResults}}">
<meta name="loc:tocFilter" content="{{__global.tocFilter}}">
<meta name="loc:nextArticle" content="{{__global.nextArticle}}">
<meta name="loc:prevArticle" content="{{__global.prevArticle}}">
<meta name="loc:themeLight" content="{{__global.themeLight}}">
<meta name="loc:themeDark" content="{{__global.themeDark}}">
<meta name="loc:themeAuto" content="{{__global.themeAuto}}">
<meta name="loc:changeTheme" content="{{__global.changeTheme}}">
<meta name="loc:copy" content="{{__global.copy}}">
<meta name="loc:downloadPdf" content="{{__global.downloadPdf}}">
{{/redirect_url}}
<script data-goatcounter="https://cssharp.goatcounter.com/count" async src="//gc.zgo.at/count.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100;0,9..40,200;0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;0,9..40,800;0,9..40,900;0,9..40,1000;1,9..40,100;1,9..40,200;1,9..40,300;1,9..40,400;1,9..40,500;1,9..40,600;1,9..40,700;1,9..40,800;1,9..40,900;1,9..40,1000&family=JetBrains+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap" rel="stylesheet">
</head>
{{^redirect_url}}
<script type="module" src="./{{_rel}}public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
{{#_googleAnalyticsTagId}}
<script async src="https://www.googletagmanager.com/gtag/js?id={{_googleAnalyticsTagId}}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', '{{_googleAnalyticsTagId}}');
</script>
{{/_googleAnalyticsTagId}}
<body class="tex2jax_ignore" data-layout="{{_layout}}{{layout}}" data-yaml-mime="{{yamlmime}}">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="{{_appLogoUrl}}{{^_appLogoUrl}}{{_rel}}index.html{{/_appLogoUrl}}">
<img id="logo" class="svg" src="{{_rel}}{{{_appLogoPath}}}{{^_appLogoPath}}logo.svg{{/_appLogoPath}}" alt="{{_appName}}" >
{{_appName}}
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
{{#_enableSearch}}
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="{{__global.search}}" autocomplete="off" aria-label="Search">
</form>
{{/_enableSearch}}
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" style="margin-top: -.65em; margin-left: -.8em"
type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas"
aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="{{uid}}">
{{!body}}
</article>
{{^_disableContribution}}
<div class="contribution d-print-none">
{{#sourceurl}}
<a href="{{sourceurl}}" class="edit-link">{{__global.improveThisDoc}}</a>
{{/sourceurl}}
{{^sourceurl}}{{#docurl}}
<a href="{{docurl}}" class="edit-link">{{__global.improveThisDoc}}</a>
{{/docurl}}{{/sourceurl}}
</div>
{{/_disableContribution}}
{{^_disableNextArticle}}
<div class="next-article d-print-none border-top" id="nextArticle"></div>
{{/_disableNextArticle}}
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
{{#_enableSearch}}
<div class="container-xxl search-results" id="search-results"></div>
{{/_enableSearch}}
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
{{{_appFooter}}}{{^_appFooter}}<span>Made with <a href="https://dotnet.github.io/docfx">docfx</a></span>{{/_appFooter}}
</div>
</div>
</footer>
</body>
{{/redirect_url}}
</html>

View File

@@ -1,4 +0,0 @@
:root {
--bs-font-sans-serif: "DM Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: "Jetbrains Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

View File

@@ -1,14 +0,0 @@
export default {
iconLinks: [
{
icon: "github",
href: "https://github.com/roflmuffin/CounterStrikeSharp",
title: "GitHub",
},
{
icon: "discord",
href: "https://discord.gg/X7r3PmuYKq",
title: "Discord",
},
],
};

View File

@@ -1,6 +0,0 @@
- name: Docs
href: docs/
- name: Examples
href: examples/
- name: API
href: api/

21
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store

6
docs/.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true
}

61
docs/astro.config.mjs Normal file
View File

@@ -0,0 +1,61 @@
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
// https://astro.build/config
export default defineConfig({
integrations: [
starlight({
title: 'CounterStrikeSharp Docs',
customCss: [
'@fontsource/dm-sans/400.css',
'@fontsource/dm-sans/500.css',
'@fontsource/dm-sans/700.css',
'@fontsource/jetbrains-mono/400.css',
'@fontsource/jetbrains-mono/600.css',
'./src/styles/custom.css',
],
head: [
{
tag: 'script',
attrs: {
src: 'https://gc.zgo.at/count.js',
'data-goatcounter': 'https://cssharp.goatcounter.com/count',
async: true,
},
},
],
social: {
github: 'https://github.com/roflmuffin/CounterStrikeSharp',
},
sidebar: [
{
label: 'Guides',
autogenerate: { directory: 'guides' },
},
{
label: 'Features',
autogenerate: { directory: 'features' },
},
{
label: 'Admin Framework',
autogenerate: { directory: 'admin-framework' },
},
{
label: 'Reference',
autogenerate: { directory: 'reference' },
},
],
editLink: {
baseUrl:
'https://github.com/roflmuffin/CounterStrikeSharp/edit/main/docs/',
},
}),
],
base: '/',
site: 'https://docs.cssharp.dev',
markdown: {
shikiConfig: {
wrap: false,
},
},
});

22
docs/package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/starlight": "^0.11.1",
"@fontsource/dm-sans": "^5.0.15",
"@fontsource/jetbrains-mono": "^5.0.15",
"astro": "^3.2.3",
"sharp": "^0.32.5"
},
"devDependencies": {
"prettier": "^3.0.3"
}
}

4118
docs/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
import { defineCollection } from 'astro:content';
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';
export const collections = {
docs: defineCollection({ schema: docsSchema() }),
i18n: defineCollection({ type: 'data', schema: i18nSchema() }),
};

View File

@@ -3,10 +3,6 @@ title: Admin Command Attributes
description: A guide on using the Admin Command Attributes in plugins.
---
# Admin Command Attributes
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.

View File

@@ -3,10 +3,6 @@ title: Defining Admins
description: A guide on how to define admins for CounterStrikeSharp.
---
# Defining Admins
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`.
@@ -30,8 +26,9 @@ Adding an Admin is as simple as creating a new entry in the `configs/admins.json
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.
:::note
All user permissions MUST start with an at-symbol @ character, otherwise CounterStrikeSharp will not recognize the permission.
:::
### Standard Permissions
@@ -58,5 +55,7 @@ However there is a somewhat standardized list of flags that it is advised you us
@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.
:::note
CounterStrikeSharp does not implement traditional admin command such as `!slay`, `!kick`, and `!ban`. It is up to individual plugins to implement these commands.
:::

View File

@@ -3,10 +3,6 @@ title: Defining Command Overrides
description: A guide on how to define command overrides for CounterStrikeSharp.
---
# Defining Command Overrides
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.

View File

@@ -3,10 +3,6 @@ title: Defining Admin Groups
description: A guide on how to define admin groups for CounterStrikeSharp.
---
# Defining Admin Groups
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.
@@ -38,9 +34,9 @@ You can add admins to groups using the `groups` array in `configs/admins.json`
}
```
> [!NOTE]
> All group names MUST start with a hashtag # character, otherwise CounterStrikeSharp won't recognize the group.
:::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`.

View File

@@ -3,10 +3,6 @@ title: Defining Admin Immunity
description: A guide on how to define immunity for admins for CounterStrikeSharp.
---
# Defining Admin Immunity
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`.
@@ -35,5 +31,6 @@ You can even assign an immunity value to groups. If an admin has a lower immunit
}
```
> [!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`.
:::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`.
:::

View File

@@ -3,10 +3,6 @@ title: Console Commands
description: How to add a new console command
---
# Console Commands
How to add a new console command
## Adding a Console Command
### Automatic registration

View File

@@ -3,10 +3,6 @@ title: Console Variables
description: How to read & write console variables (ConVars).
---
# Console Variables
How to read & write console variables (ConVars).
## Finding a ConVar
Use the `ConVar.Find` static method to find a reference to an existing ConVar (or `null`).

View File

@@ -3,18 +3,15 @@ title: Game Events
description: How to listen to Source 1 style game events.
---
# Game Events
How to listen to Source 1 style game events.
## Adding an Event Listener
### Automatic registration
CounterStrikeSharp will automatically register event listeners marked with a `GameEventHandler` attribute on the `BasePlugin` class. These listeners are automatically registered/deregistered for you on hot reload.
> [!NOTE]
> The first parameter type must be a subclass of the `GameEvent` class. The names are automatically generated from the [game event list](https://cs2.poggu.me/dumped-data/game-events).
:::note
The first parameter type must be a subclass of the `GameEvent` class. The names are automatically generated from the [game event list](https://cs2.poggu.me/dumped-data/game-events).
:::
```csharp
[GameEventHandler]

View File

@@ -3,10 +3,6 @@ title: Global Listeners
description: How to subscribe to CounterStrikeSharp global listeners.
---
# Global Listeners
How to subscribe to CounterStrikeSharp global listeners.
## Adding a Listener
Global listeners come in a variety of shapes so there is no automatic registration for these, they must be registered in the `OnLoad` of your plugin (or anywhere you have access to the plugin instance). You can find the full list of event listeners in the `Listeners` class as seen below.

View File

@@ -1,12 +1,10 @@
---
title: Dependency Injection
description: How to make use of dependency injection in CounterStrikeSharp
sidebar:
order: 1
---
# Dependency Injection
How to make use of dependency injection in CounterStrikeSharp
`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.

View File

@@ -0,0 +1,56 @@
---
title: Getting Started
description: How to get started installing & using CounterStrikeSharp.
sidebar:
order: 0
---
# Installation
### Installing Metamod
`CounterStrikeSharp` uses `Metamod:Source` as its main way of communicating with the game server. To install it, you can follow the detailed instructions found <a href="https://cs2.poggu.me/metamod/installation/" target="_blank">here</a>.
### Installing CounterStrikeSharp
Download the latest release of CounterStrikeSharp from <a href="https://github.com/roflmuffin/CounterStrikeSharp/actions/workflows/cmake-single-platform.yml" target="_blank">GitHub actions build pages</a> (just choose the latest development snapshot). **You may need to be logged into GitHub to gain access to the downloads**.
:::caution[.NET Runtime]
If this is your first time installing, you will need to download the `with-runtime` version. This includes a copy of the .NET runtime, which is required to run the plugin.
Depending on the os you might also either need to install `libicu` / `icu-libs` / `libicu-dev` using your package manager for .NET to run or setting `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true` in your servers environment variables. You can find more infos about that <a href="https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#enabling-the-invariant-mode" target="_blank">here</a>
Subsequent upgrades will not require the runtime, unless a version bump of the .NET runtime is required (i.e. from 7.0.x to 8.0.x). We will inform you when this occurs.
:::
Extract the `addons` folder to the `/csgo/` directory of the dedicated server. The contents of your addons folder should contain both the `counterstrikesharp` folder and the `metamod` folder as seen below.
```shell
<server_path>/game/csgo/addons > tree -L 2
addons
├── counterstrikesharp
│   ├── api
│   ├── bin
│   ├── dotnet
│   ├── plugins
│ └── gamedata
├── metamod
│   ├── bin
│   ├── counterstrikesharp.vdf
│   ├── metaplugins.ini
│   └── README.txt
├── metamod.vdf
└── metamod_x64.vdf
```
### Start the Server
Launch your CS2 dedicated server as normal. If everything is working correctly, you should see a message in the console that says `CSSharp: CounterStrikeSharp.API Loaded Successfully.`.
Running the command `meta list` in the console should show 1 plugin loaded 🎉
```shell
meta list
Listing 1 plugin:
[01] CounterStrikeSharp (0.1.0) by Roflmuffin
```

View File

@@ -1,12 +1,10 @@
---
title: Hello World Plugin
description: How to write your first plugin for CounterStrikeSharp
sidebar:
order: 0
---
# Hello World Plugin
How to write your first plugin for CounterStrikeSharp
## Creating a New Project
First, ensure you have the relevant .NET 7.0 SDK for your platform installed on your machine. You can find the links to the latest downloads on the <a href="https://dotnet.microsoft.com/en-us/download/dotnet/7.0" target="_blank"> official Microsoft download page</a>.
@@ -38,11 +36,12 @@ 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
> ```
:::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
@@ -79,17 +78,20 @@ Locate the `plugins` folder in your CS2 dedicated server (`/game/csgo/addons/cou
└── 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.
:::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.
:::note
Note that some of these dependencies may change depending on the version of CounterStrikeSharp being used.
:::
### Start the Server
Now start your CS2 dedicated server. Just before the `CounterStrikeSharp.API Loaded Successfully.` message you should see your `Hello World!` console write that we called from the load function, neat!
> [!NOTE]
> By default, CounterStrikeSharp will automatically hot reload your plugin if you replace the .dll file in your plugin folder. When it does so, it will call the `Unload` and `Load` functions respectively, with the `hotReload` flag set to true.
>
> It is worth noting that the framework will automatically deregister any event handlers or listeners for you automatically, so you can safely reregister these on load without checking this flag. However you may want to do some specific actions on a hot reload, which you can do in your `Load()` call by checking the flag!
:::note[Hot Reloading!]
By default, CounterStrikeSharp will automatically hot reload your plugin if you replace the .dll file in your plugin folder. When it does so, it will call the `Unload` and `Load` functions respectively, with the `hotReload` flag set to true.
It is worth noting that the framework will automatically deregister any event handlers or listeners for you automatically, so you can safely reregister these on load without checking this flag. However you may want to do some specific actions on a hot reload, which you can do in your `Load()` call by checking the flag!
:::

View File

@@ -0,0 +1,52 @@
---
title: CounterStrikeSharp
description: Write Counter-Strike 2 server plugins in C#.
template: splash
hero:
tagline: <code class="csharp">CounterStrikeSharp</code> is a simpler way to write CS2 server plugins.
actions:
- text: Get started
link: ./guides/getting-started/
icon: right-arrow
variant: primary
- text: Browse source
link: https://github.com/roflmuffin/CounterStrikeSharp/
icon: github
---
```csharp
using CounterStrikeSharp.API.Core;
namespace HelloWorldPlugin;
public class HelloWorldPlugin : BasePlugin
{
public override string ModuleName => "Hello World Plugin";
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)
{
Logger.LogInformation("Plugin loaded successfully!");
}
[GameEventHandler]
public HookResult OnPlayerConnect(EventPlayerConnect @event, GameEventInfo info)
{
// Userid will give you a reference to a CCSPlayerController class
Logger.LogInformation("Player {Name} has connected!", @event.Userid.PlayerName);
return HookResult.Continue;
}
[ConsoleCommand("issue_warning", "Issue warning to player")]
public void OnCommand(CCSPlayerController? player, CommandInfo command)
{
Logger.LogWarning("Player shouldn't be doing that");
}
}
```

View File

@@ -3,10 +3,6 @@ title: Core Configuration
description: Summary for core configuration values
---
# Core Configuration
Summary for core configuration values
## PublicChatTrigger
List of characters to use for public chat triggers.
@@ -25,9 +21,9 @@ Enabling this option will block plugins from using functionality that is known t
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.
:::note
Disable this option at your own risk.
:::
## PluginHotReloadEnabled

View File

@@ -3,10 +3,6 @@ title: Referencing Players
description: Difference between player slots, indexes, userids, controllers & pawns.
---
# Referencing Players
Difference between player slots, indexes, userids, controllers & pawns.
## Controllers & Pawns
All players in CS2 are split between a player controller & a player pawn. The player controller represents the player on the server, and the player pawn represents the players physical character in the game world. This means to edit a players health for example, you would need to edit their `PlayerPawn`'s health; but to check for a player's SteamID, you would check the `PlayerController`.
@@ -48,12 +44,13 @@ var player = Utilities.GetPlayerFromIndex(index);
var player = Utilities.GetPlayerFromSlot(slot);
```
> [!NOTE]
> Wherever possible, you should check the validity of any handle you are accessing before assuming it is safe to use.
> ```csharp
> RegisterEventHandler<EventPlayerSpawn>((@event, info) =>
> {
> if (!@event.Userid.IsValid) return 0; // Checks that the PlayerController is valid
> if (!@event.Userid.PlayerPawn.IsValid) return 0; // Checks that the value of the CHandle is pointing to a valid PlayerPawn.
> }
> ```
:::note[Entity Safety]
Wherever possible, you should check the validity of any handle you are accessing before assuming it is safe to use.
```csharp
RegisterEventHandler<EventPlayerSpawn>((@event, info) =>
{
if (!@event.Userid.IsValid) return 0; // Checks that the PlayerController is valid
if (!@event.Userid.PlayerPawn.IsValid) return 0; // Checks that the value of the CHandle is pointing to a valid PlayerPawn.
}
```
:::

2
docs/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View File

@@ -0,0 +1,19 @@
:root {
--sl-font: 'DM Sans', serif;
--sl-font-mono: 'Jetbrains Mono', monospace;
}
.hero {
grid-template-columns: 1fr;
}
.hero .copy {
align-items: center;
}
.hero .actions {
justify-content: center;
}
.hero {
}

3
docs/tsconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "astro/tsconfigs/strict"
}

View File

@@ -1,2 +1,2 @@
# With Config
# WithConfig
This example shows how you can implement the `IPluginConfig` interface to allow for semi-automatic config parsing & loading.

View File

@@ -1,2 +0,0 @@
# With Database (Dapper)
Simple SQLite database example using Dapper library to track kills.

View File

@@ -1,16 +0,0 @@
<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>

View File

@@ -1,89 +0,0 @@
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}"); });
});
}
}

View File

@@ -1,2 +0,0 @@
# With Entity Output Hooks
This example shows how to implement hooks for entity output, such as StartTouch, OnPickup etc.

View File

@@ -1,12 +0,0 @@
<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>

View File

@@ -1,43 +0,0 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using Microsoft.Extensions.Logging;
namespace WithEntityOutputHooks;
[MinimumApiVersion(80)]
public class WithEntityOutputHooksPlugin : BasePlugin
{
public override string ModuleName => "Example: With Entity Output Hooks";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "A simple plugin that showcases entity output hooks";
public override void Load(bool hotReload)
{
HookEntityOutput("weapon_knife", "OnPlayerPickup", (CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay) =>
{
Logger.LogInformation("weapon_knife called OnPlayerPickup ({name}, {activator}, {caller}, {delay})", name, activator.DesignerName, caller.DesignerName, delay);
return HookResult.Continue;
});
}
// Output hooks can use wildcards to match multiple entities
[EntityOutputHook("*", "OnPlayerPickup")]
public HookResult OnPickup(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay)
{
Logger.LogInformation("[EntityOutputHook Attribute] Called OnPlayerPickup ({name}, {activator}, {caller}, {delay})", name, activator.DesignerName, caller.DesignerName, delay);
return HookResult.Continue;
}
// Output hooks can use wildcards to match multiple output names
[EntityOutputHook("func_buyzone", "*")]
public HookResult OnTouchStart(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay)
{
Logger.LogInformation("[EntityOutputHook Attribute] Buyzone called output ({name}, {activator}, {caller}, {delay})", name, activator.DesignerName, caller.DesignerName, delay);
return HookResult.Continue;
}
}

View File

@@ -157,30 +157,6 @@ 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();
@@ -479,16 +455,6 @@ 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();
@@ -643,37 +609,24 @@ namespace CounterStrikeSharp.API.Core
}
}
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 HookEntityOutput(string classname, string outputname, InputArgument callback, HookMode mode){
public static void HookEntityOutput(string classname, string outputname, InputArgument callback){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(classname);
ScriptContext.GlobalScriptContext.Push(outputname);
ScriptContext.GlobalScriptContext.Push((InputArgument)callback);
ScriptContext.GlobalScriptContext.Push(mode);
ScriptContext.GlobalScriptContext.SetIdentifier(0x15245242);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}
public static void UnhookEntityOutput(string classname, string outputname, InputArgument callback, HookMode mode){
public static void UnhookEntityOutput(string classname, string outputname, InputArgument callback){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(classname);
ScriptContext.GlobalScriptContext.Push(outputname);
ScriptContext.GlobalScriptContext.Push((InputArgument)callback);
ScriptContext.GlobalScriptContext.Push(mode);
ScriptContext.GlobalScriptContext.SetIdentifier(0x87DBD139);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
@@ -1113,16 +1066,6 @@ 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();

View File

@@ -462,20 +462,20 @@ namespace CounterStrikeSharp.API.Core
}
}
public void HookEntityOutput(string classname, string outputName, EntityIO.EntityOutputHandler handler, HookMode mode = HookMode.Pre)
public void HookEntityOutput(string classname, string outputName, EntityIO.EntityOutputHandler handler)
{
var subscriber = new CallbackSubscriber(handler, handler,
() => UnhookEntityOutput(classname, outputName, handler));
NativeAPI.HookEntityOutput(classname, outputName, subscriber.GetInputArgument(), mode);
NativeAPI.HookEntityOutput(classname, outputName, subscriber.GetInputArgument());
EntityOutputHooks[handler] = subscriber;
}
public void UnhookEntityOutput(string classname, string outputName, EntityIO.EntityOutputHandler handler, HookMode mode = HookMode.Pre)
public void UnhookEntityOutput(string classname, string outputName, EntityIO.EntityOutputHandler handler)
{
if (!EntityOutputHooks.TryGetValue(handler, out var subscriber)) return;
NativeAPI.UnhookEntityOutput(classname, outputName, subscriber.GetInputArgument(), mode);
NativeAPI.UnhookEntityOutput(classname, outputName, subscriber.GetInputArgument());
FunctionReference.Remove(subscriber.GetReferenceIdentifier());
EntityOutputHooks.Remove(handler);
}
@@ -484,14 +484,12 @@ namespace CounterStrikeSharp.API.Core
{
// since we wrap around the plugin handler we need to do this to ensure that the plugin callback is only called
// if the entity instance is the same.
EntityIO.EntityOutputHandler internalHandler = (output, name, activator, caller, value, delay) =>
EntityIO.EntityOutputHandler internalHandler = (string name, CEntityInstance activator, CEntityInstance caller, float delay) =>
{
if (caller == entityInstance)
{
return handler(output, name, activator, caller, value, delay);
handler(name, activator, caller, delay);
}
return HookResult.Continue;
};
HookEntityOutput(entityInstance.DesignerName, outputName, internalHandler);

View File

@@ -126,26 +126,5 @@ 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);
}
}

View File

@@ -1,6 +1,4 @@
using System;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory;
@@ -52,15 +50,13 @@ 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, int duration)
public void PrintToCenterHtml(string message)
{
var @event = new EventShowSurvivalRespawnStatus(true)
{
LocToken = message,
Duration = duration,
Duration = 5,
Userid = this
};
@event.FireEventToClient(this);
@@ -144,57 +140,6 @@ 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>
@@ -218,20 +163,4 @@ public partial class CCSPlayerController
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;
}
}
}

View File

@@ -1,49 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Core;
public partial class CEntityIOOutput
{
public EntityIOConnection_t? Connections => Utilities.GetPointer<EntityIOConnection_t>(Handle + 8);
public EntityIOOutputDesc_t Description => new(Marshal.ReadIntPtr(Handle + 16));
}
public class EntityIOOutputDesc_t : NativeObject
{
public EntityIOOutputDesc_t(IntPtr pointer) : base(pointer)
{
}
public string Name => Utilities.ReadStringUtf8(Handle + 0);
public unsafe ref uint Flags => ref Unsafe.AsRef<uint>((void*)(Handle + 8));
public unsafe ref uint OutputOffset => ref Unsafe.AsRef<uint>((void*)(Handle + 16));
}
public class EntityIOConnection_t : EntityIOConnectionDesc_t
{
public EntityIOConnection_t(IntPtr pointer) : base(pointer)
{
}
public unsafe ref bool MarkedForRemoval => ref Unsafe.AsRef<bool>((void*)(Handle + 40));
public EntityIOConnection_t? Next => Utilities.GetPointer<EntityIOConnection_t>(Handle + 48);
}
public class EntityIOConnectionDesc_t : NativeObject
{
public EntityIOConnectionDesc_t(IntPtr pointer) : base(pointer)
{
}
public string TargetDesc => Utilities.ReadStringUtf8(Handle + 0);
public string TargetInput => Utilities.ReadStringUtf8(Handle + 8);
public string ValueOverride => Utilities.ReadStringUtf8(Handle + 16);
public CEntityHandle Target => new(Handle + 24);
public unsafe ref EntityIOTargetType_t TargetType => ref Unsafe.AsRef<EntityIOTargetType_t>((void*)(Handle + 28));
public unsafe ref int TimesToFire => ref Unsafe.AsRef<int>((void*)(Handle + 32));
public unsafe ref float Delay => ref Unsafe.AsRef<float>((void*)(Handle + 36));
}

View File

@@ -36,12 +36,12 @@ public partial class CEntityInstance : IEquatable<CEntityInstance>
public bool Equals(CEntityInstance? other)
{
return this.EntityHandle.Equals(other?.EntityHandle);
return this.EntityHandle == other?.EntityHandle;
}
public override bool Equals(object? obj)
{
return obj is CEntityInstance other && Equals(other);
return ReferenceEquals(this, obj) || obj is CEntityInstance other && Equals(other);
}
public override int GetHashCode()

View File

@@ -1,13 +0,0 @@
namespace CounterStrikeSharp.API.Core;
/// <summary>
/// Placeholder for CVariant
/// <see href="https://github.com/alliedmodders/hl2sdk/blob/cs2/public/variant.h"/>
/// <remarks>A lot of entity outputs do not use this value</remarks>
/// </summary>
public class CVariant : NativeObject
{
public CVariant(IntPtr pointer) : base(pointer)
{
}
}

View File

@@ -73,14 +73,11 @@ namespace CounterStrikeSharp.API.Core.Plugin
_fileWatcher.Deleted += async (s, e) =>
{
Server.NextWorldUpdate(() =>
if (e.FullPath == path)
{
if (e.FullPath == path)
{
_logger.LogInformation("Plugin {Name} has been deleted, unloading...", Plugin.ModuleName);
Unload(true);
}
});
_logger.LogInformation("Plugin {Name} has been deleted, unloading...", Plugin.ModuleName);
Unload(true);
}
};
_fileWatcher.Filter = "*.dll";
@@ -91,14 +88,11 @@ namespace CounterStrikeSharp.API.Core.Plugin
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);
});
_logger.LogInformation("Reloading plugin {Name}", Plugin.ModuleName);
Loader = eventargs.Loader;
Unload(hotReload: true);
Load(hotReload: true);
return Task.CompletedTask;
}

View File

@@ -94,7 +94,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// The server console should have access to all commands, regardless of groups.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData((SteamID)player.AuthorizedSteamID);
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
return playerData?.Groups.IsSupersetOf(groups) ?? false;
}
@@ -119,7 +119,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
AddPlayerToGroup((SteamID)player.AuthorizedSteamID, groups);
AddPlayerToGroup((SteamID)player.SteamID, groups);
}
/// <summary>
@@ -161,7 +161,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
RemovePlayerFromGroup((SteamID)player.AuthorizedSteamID, true, groups);
RemovePlayerFromGroup((SteamID)player.SteamID, true, groups);
}
/// <summary>

View File

@@ -102,7 +102,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData(player.AuthorizedSteamID);
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
return playerData?.Flags.IsSupersetOf(flags) ?? false;
}
@@ -136,7 +136,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData((SteamID)player.AuthorizedSteamID);
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
return playerData?.CommandOverrides.ContainsKey(command) ?? false;
}
@@ -165,7 +165,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData((SteamID)player.AuthorizedSteamID);
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
return playerData?.CommandOverrides.GetValueOrDefault(command) ?? false;
}
@@ -193,7 +193,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// The server console should have access to all commands, regardless of permissions.
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
SetPlayerCommandOverride((SteamID)player.AuthorizedSteamID, command, state);
SetPlayerCommandOverride((SteamID)player.SteamID, command, state);
}
/// <summary>
@@ -235,7 +235,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
AddPlayerPermissions((SteamID)player.AuthorizedSteamID, flags);
AddPlayerPermissions((SteamID)player.SteamID, flags);
}
/// <summary>
@@ -278,7 +278,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
RemovePlayerPermissions((SteamID)player.AuthorizedSteamID, flags);
RemovePlayerPermissions((SteamID)player.SteamID, flags);
}
/// <summary>
@@ -306,7 +306,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
ClearPlayerPermissions((SteamID)player.AuthorizedSteamID);
ClearPlayerPermissions((SteamID)player.SteamID);
}
/// <summary>
@@ -335,7 +335,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
SetPlayerImmunity((SteamID)player.AuthorizedSteamID, value);
SetPlayerImmunity((SteamID)player.SteamID, value);
}
/// <summary>
@@ -366,10 +366,10 @@ namespace CounterStrikeSharp.API.Modules.Admin
if (target == null) return false;
if (!target.IsValid || target.Connected != PlayerConnectedState.PlayerConnected) return false;
var callerData = GetPlayerAdminData((SteamID)caller.AuthorizedSteamID);
var callerData = GetPlayerAdminData((SteamID)caller.SteamID);
if (callerData == null) return false;
var targetData = GetPlayerAdminData((SteamID)target.AuthorizedSteamID);
var targetData = GetPlayerAdminData((SteamID)target.SteamID);
if (targetData == null) return true;
return callerData.Immunity >= targetData.Immunity;

View File

@@ -31,8 +31,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
// If we have a command in the "command_overrides" section in "configs/admins.json",
// we skip the checks below and just return the defined value.
if (caller?.AuthorizedSteamID == null) return false;
var adminData = AdminManager.GetPlayerAdminData(caller.AuthorizedSteamID);
var adminData = AdminManager.GetPlayerAdminData((SteamID)caller.SteamID);
if (adminData == null) return false;
if (adminData.CommandOverrides.ContainsKey(Command))
{

View File

@@ -23,7 +23,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
var groupPermissions = Permissions.Where(perm => perm.StartsWith(PermissionCharacters.GroupPermissionChar));
var userPermissions = Permissions.Where(perm => perm.StartsWith(PermissionCharacters.UserPermissionChar));
var adminData = AdminManager.GetPlayerAdminData(caller.AuthorizedSteamID);
var adminData = AdminManager.GetPlayerAdminData((SteamID)caller.SteamID);
if (adminData == null) return false;
return (groupPermissions.Intersect(adminData.Groups).Count() + userPermissions.Intersect(adminData.Flags).Count()) > 0;
}

View File

@@ -1,32 +0,0 @@
namespace CounterStrikeSharp.API.Modules.Entities.Constants;
/// <summary>
/// Represents the instance types for a Steam account.
/// </summary>
public enum SteamAccountInstance
{
/// <summary>
/// Invalid instance.
/// </summary>
Invalid = -1,
/// <summary>
/// All instances.
/// </summary>
All = 0,
/// <summary>
/// Desktop instance.
/// </summary>
Desktop = 1,
/// <summary>
/// Console instance.
/// </summary>
Console = 2,
/// <summary>
/// Web instance.
/// </summary>
Web = 4
}

View File

@@ -1,70 +0,0 @@
using System.Runtime.Serialization;
namespace CounterStrikeSharp.API.Modules.Entities.Constants;
/// <summary>
/// Represents the types of Steam accounts.
/// </summary>
public enum SteamAccountType
{
/// <summary>
/// Invalid account type.
/// </summary>
[EnumMember(Value = "I")]
Invalid = 0,
/// <summary>
/// Individual account type.
/// </summary>
[EnumMember(Value = "U")]
Individual,
/// <summary>
/// MultiSeat account type.
/// </summary>
[EnumMember(Value = "M")]
MultiSeat,
/// <summary>
/// Game Server account type.
/// </summary>
[EnumMember(Value = "G")]
GameServer,
/// <summary>
/// Anonymous Game Server account type.
/// </summary>
[EnumMember(Value = "A")]
AnonGameServer,
/// <summary>
/// Pending account type.
/// </summary>
[EnumMember(Value = "P")]
Pending,
/// <summary>
/// Content Server account type.
/// </summary>
[EnumMember(Value = "C")]
ContentServer,
/// <summary>
/// Clan account type.
/// </summary>
[EnumMember(Value = "g")]
Clan,
/// <summary>
/// Chat account type.
/// </summary>
[EnumMember(Value = "T")]
Chat,
/// <summary>
/// Console user account type.
/// </summary>
[EnumMember(Value = "c")]
ConsoleUser,
/// <summary>
/// P2P Super Seeder account type.
/// </summary>
[EnumMember(Value = " ")]
P2PSuperSeeder,
/// <summary>
/// Anonymous user account type.
/// </summary>
[EnumMember(Value = "a")]
AnonUser,
}

View File

@@ -1,32 +0,0 @@
namespace CounterStrikeSharp.API.Modules.Entities.Constants;
/// <summary>
/// Represents the universe of a Steam account.
/// </summary>
public enum SteamAccountUniverse
{
/// <summary>
/// Individual / unspecified universe.
/// </summary>
Unspecified = 0,
/// <summary>
/// Public universe.
/// </summary>
Public = 1,
/// <summary>
/// Beta universe.
/// </summary>
Beta = 2,
/// <summary>
/// Internal universe.
/// </summary>
Internal = 3,
/// <summary>
/// Development universe.
/// </summary>
Dev = 4,
}

View File

@@ -18,7 +18,7 @@ namespace CounterStrikeSharp.API.Modules.Entities
{
public class EntityIO
{
public delegate HookResult EntityOutputHandler(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay);
public delegate void EntityOutputHandler(string name, CEntityInstance activator, CEntityInstance caller, float delay);
internal class EntityOutputCallback
{

View File

@@ -1,5 +1,4 @@
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Utils;
using System;
namespace CounterStrikeSharp.API.Modules.Entities
{
@@ -13,7 +12,6 @@ namespace CounterStrikeSharp.API.Modules.Entities
public static explicit operator SteamID(ulong u) => new(u);
public static explicit operator SteamID(string s) => new(s);
ulong ParseId(string id)
{
var parts = id.Split(':');
@@ -36,7 +34,7 @@ namespace CounterStrikeSharp.API.Modules.Entities
public string SteamId3
{
get => $"[{EnumUtils.GetEnumMemberAttributeValue(AccountType)}:{(int)AccountUniverse}:{SteamId64 - Base}]";
get => $"[U:1:{SteamId64 - Base}]";
set => SteamId64 = ParseId3(value);
}
@@ -46,54 +44,20 @@ namespace CounterStrikeSharp.API.Modules.Entities
set => SteamId64 = (ulong)value + Base;
}
public int AccountId => (int)((SteamId64 >> 0) & 0xFFFFFFFF);
public SteamAccountInstance AccountInstance =>
(SteamAccountInstance)((SteamId64 >> 32) & 0xFFFFF);
public SteamAccountType AccountType =>
(SteamAccountType)((SteamId64 >> 52) & 0xF);
public SteamAccountUniverse AccountUniverse =>
(SteamAccountUniverse)((SteamId64 >> 56) & 0xF);
public bool IsValid()
{
if (AccountUniverse == SteamAccountUniverse.Unspecified
|| AccountType == SteamAccountType.Invalid
|| AccountInstance == SteamAccountInstance.Invalid)
return false;
if (AccountType == SteamAccountType.Individual
&& (AccountId == 0 || AccountInstance != SteamAccountInstance.Desktop))
return false;
if (AccountType == SteamAccountType.Clan
&& (AccountId == 0 || AccountInstance != SteamAccountInstance.All))
return false;
if (AccountType == SteamAccountType.GameServer && AccountId == 0)
return false;
return true;
}
public override string ToString() => $"[SteamID {SteamId64}, {SteamId2}, {SteamId3}]";
public Uri ToCommunityUrl()
{
string url = string.Empty;
if (AccountType == SteamAccountType.Individual)
url = "https://steamcommunity.com/profiles/" + SteamId64;
if (AccountType == SteamAccountType.Clan)
url = "https://steamcommunity.com/gid/" + SteamId64;
return new Uri(url);
}
public bool Equals(SteamID? other)
{
return other != null && SteamId64 == other.SteamId64;
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return SteamId64 == other.SteamId64;
}
public override bool Equals(object? obj)
{
if (obj?.GetType() != this.GetType()) return false;
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((SteamID)obj);
}

View File

@@ -94,7 +94,7 @@ public partial class VirtualFunctionVoid<TArg1, TArg2, TArg3>
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3>(signature);
}
public VirtualFunctionVoid(string signature, string binarypath)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3>(signature, binarypath);

View File

@@ -99,7 +99,7 @@ public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TResult>
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TResult>(signature, binarypath);
@@ -124,7 +124,7 @@ public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TResu
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TResult>(signature, binarypath);
@@ -149,7 +149,7 @@ public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TArg5
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TResult>(signature, binarypath);
@@ -174,7 +174,7 @@ public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TArg5
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult>(signature, binarypath);
@@ -199,7 +199,7 @@ public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TArg5
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult>(signature, binarypath);
@@ -224,7 +224,7 @@ public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TArg5
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult>(signature, binarypath);
@@ -249,7 +249,7 @@ public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TArg5
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult>(signature, binarypath);
@@ -274,7 +274,7 @@ public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TArg5
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult>(signature, binarypath);

View File

@@ -62,12 +62,17 @@ public class CHandle<T> : IEquatable<CHandle<T>> where T : NativeEntity
public bool Equals(CHandle<T>? other)
{
return other != null && Raw == other.Raw;
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Raw == other.Raw;
}
public override bool Equals(object? obj)
{
return Equals(obj as CHandle<T>);
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((CHandle<T>)obj);
}
public override int GetHashCode()

View File

@@ -45,28 +45,12 @@ namespace CounterStrikeSharp.API
// Currently only used to keep the delegate from being garbage collected
private static List<Action> nextFrameTasks = new List<Action>();
/// <summary>
/// Queue a task to be executed on the next game frame.
/// <remarks>Does not execute if the server is hibernating.</remarks>
/// </summary>
public static void NextFrame(Action task)
{
nextFrameTasks.Add(task);
var ptr = Marshal.GetFunctionPointerForDelegate(task);
NativeAPI.QueueTaskForNextFrame(ptr);
}
/// <summary>
/// Queue a task to be executed on the next pre world update.
/// <remarks>Executes if the server is hibernating.</remarks>
/// </summary>
/// <param name="task"></param>
public static void NextWorldUpdate(Action task)
{
nextFrameTasks.Add(task);
var ptr = Marshal.GetFunctionPointerForDelegate(task);
NativeAPI.QueueTaskForNextWorldUpdate(ptr);
}
public static void PrintToChatAll(string message)
{

View File

@@ -67,7 +67,7 @@ namespace CounterStrikeSharp.API
public static CCSPlayerController? GetPlayerFromSteamId(ulong steamId)
{
return Utilities.GetPlayers().FirstOrDefault(player => player.AuthorizedSteamID == (SteamID)steamId);
return Utilities.GetPlayers().FirstOrDefault(player => player.SteamID == steamId);
}
public static TargetResult ProcessTargetString(string pattern, CCSPlayerController player)
@@ -150,16 +150,5 @@ namespace CounterStrikeSharp.API
return Encoding.UTF8.GetString(buffer);
}
}
public static T? GetPointer<T>(IntPtr pointer) where T : NativeObject
{
var pointerTo = Marshal.ReadIntPtr(pointer);
if (pointerTo == IntPtr.Zero)
{
return null;
}
return (T)Activator.CreateInstance(typeof(T), pointerTo)!;
}
}
}

View File

@@ -22,10 +22,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithCommands", "..\examples
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithGameEventHandlers", "..\examples\WithGameEventHandlers\WithGameEventHandlers.csproj", "{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithDatabaseDapper", "..\examples\WithDatabaseDapper\WithDatabaseDapper.csproj", "{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithEntityOutputHooks", "..\examples\WithEntityOutputHooks\WithEntityOutputHooks.csproj", "{31EABE0B-871F-497B-BF36-37FFC6FAD15F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -72,14 +68,6 @@ Global
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}.Release|Any CPU.Build.0 = Release|Any CPU
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B}.Release|Any CPU.Build.0 = Release|Any CPU
{31EABE0B-871F-497B-BF36-37FFC6FAD15F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31EABE0B-871F-497B-BF36-37FFC6FAD15F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31EABE0B-871F-497B-BF36-37FFC6FAD15F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31EABE0B-871F-497B-BF36-37FFC6FAD15F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{57E64289-5D69-4AA1-BEF0-D0D96A55EE8F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
@@ -89,7 +77,5 @@ Global
{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{EA2F596E-2236-4999-B476-B1FDA287674A} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{31EABE0B-871F-497B-BF36-37FFC6FAD15F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
EndGlobalSection
EndGlobal

View File

@@ -409,24 +409,9 @@ namespace TestPlugin
private void SetupEntityOutputHooks()
{
HookEntityOutput("weapon_knife", "OnPlayerPickup", (output, name, activator, caller, value, delay) =>
HookEntityOutput("weapon_knife", "OnPlayerPickup", (string name, CEntityInstance activator, CEntityInstance caller, float delay) =>
{
Logger.LogInformation("weapon_knife called OnPlayerPickup ({name}, {activator}, {caller}, {delay})", output.Description.Name, activator.DesignerName, caller.DesignerName, delay);
return HookResult.Continue;
});
HookEntityOutput("*", "*", (output, name, activator, caller, value, delay) =>
{
Logger.LogInformation("All EntityOutput ({name}, {activator}, {caller}, {delay})", output.Description.Name, activator.DesignerName, caller.DesignerName, delay);
return HookResult.Continue;
});
HookEntityOutput("*", "OnStartTouch", (output, name, activator, caller, value, delay) =>
{
Logger.LogInformation("OnStartTouch: ({name}, {activator}, {caller}, {delay})", name, activator.DesignerName, caller.DesignerName, delay);
return HookResult.Continue;
Logger.LogInformation("weapon_knife called OnPlayerPickup ({name}, {activator}, {caller}, {delay})", name, activator.DesignerName, caller.DesignerName, delay);
});
}
@@ -569,12 +554,10 @@ namespace TestPlugin
return HookResult.Continue;
}
[EntityOutputHook("*", "OnPlayerPickup")]
public HookResult OnPickup(CEntityIOOutput output, string name, CEntityInstance activator, CEntityInstance caller, CVariant value, float delay)
[EntityOutputHook("weapon_knife", "OnPlayerPickup")]
public void OnKnifePickup(string name, CEntityInstance activator, CEntityInstance caller, float delay)
{
Logger.LogInformation("[EntityOutputHook Attribute] Called OnPlayerPickup ({name}, {activator}, {caller}, {delay})", name, activator.DesignerName, caller.DesignerName, delay);
return HookResult.Continue;
Logger.LogInformation("[EntityOutputHook Attribute] weapon_knife called OnPlayerPickup ({name}, {activator}, {caller}, {delay})", name, activator.DesignerName, caller.DesignerName, delay);
}
}
}

View File

@@ -78,7 +78,6 @@ ChatManager chatManager;
ServerManager serverManager;
GetLegacyGameEventListener_t* GetLegacyGameEventListener = nullptr;
std::thread::id gameThreadId;
void Initialize()
{

View File

@@ -6,8 +6,6 @@
#undef protected
#undef private
#include <thread>
#include "ISmmAPI.h"
#include "eiface.h"
#include "iserver.h"
@@ -109,7 +107,6 @@ extern CGameConfig* gameConfig;
typedef IGameEventListener2 *GetLegacyGameEventListener_t(CPlayerSlot slot);
extern GetLegacyGameEventListener_t* GetLegacyGameEventListener;
extern std::thread::id gameThreadId;
void Initialize();
// Should only be called within the active game loop (i e map should be loaded

View File

@@ -19,7 +19,6 @@
#include "core/log.h"
#include <funchook.h>
#include <vector>
#include <public/eiface.h>
#include "scripting/callback_manager.h"
@@ -30,19 +29,17 @@ EntityManager::EntityManager() {}
EntityManager::~EntityManager() {}
void EntityManager::OnAllInitialized()
{
void EntityManager::OnAllInitialized() {
on_entity_spawned_callback = globals::callbackManager.CreateCallback("OnEntitySpawned");
on_entity_created_callback = globals::callbackManager.CreateCallback("OnEntityCreated");
on_entity_deleted_callback = globals::callbackManager.CreateCallback("OnEntityDeleted");
on_entity_parent_changed_callback =
globals::callbackManager.CreateCallback("OnEntityParentChanged");
on_entity_parent_changed_callback = globals::callbackManager.CreateCallback("OnEntityParentChanged");
m_pFireOutputInternal = reinterpret_cast<FireOutputInternal>(modules::server->FindSignature(
globals::gameConfig->GetSignature("CEntityIOOutput_FireOutputInternal")));
m_pFireOutputInternal = reinterpret_cast<FireOutputInternal>(
modules::server->FindSignature(globals::gameConfig->GetSignature("CEntityIOOutput_FireOutputInternal")));
if (m_pFireOutputInternal == nullptr) {
CSSHARP_CORE_CRITICAL("Failed to find signature for \'CEntityIOOutput_FireOutputInternal\'");
CSSHARP_CORE_ERROR("Failed to find signature for \'CEntityIOOutput_FireOutputInternal\'");
return;
}
@@ -53,8 +50,7 @@ void EntityManager::OnAllInitialized()
// Listener is added in ServerStartup as entity system is not initialised at this stage.
}
void EntityManager::OnShutdown()
{
void EntityManager::OnShutdown() {
globals::callbackManager.ReleaseCallback(on_entity_spawned_callback);
globals::callbackManager.ReleaseCallback(on_entity_created_callback);
globals::callbackManager.ReleaseCallback(on_entity_deleted_callback);
@@ -62,8 +58,7 @@ void EntityManager::OnShutdown()
globals::entitySystem->RemoveListenerEntity(&entityListener);
}
void CEntityListener::OnEntitySpawned(CEntityInstance* pEntity)
{
void CEntityListener::OnEntitySpawned(CEntityInstance *pEntity) {
auto callback = globals::entityManager.on_entity_spawned_callback;
if (callback && callback->GetFunctionCount()) {
@@ -72,8 +67,7 @@ void CEntityListener::OnEntitySpawned(CEntityInstance* pEntity)
callback->Execute();
}
}
void CEntityListener::OnEntityCreated(CEntityInstance* pEntity)
{
void CEntityListener::OnEntityCreated(CEntityInstance *pEntity) {
auto callback = globals::entityManager.on_entity_created_callback;
if (callback && callback->GetFunctionCount()) {
@@ -82,8 +76,7 @@ void CEntityListener::OnEntityCreated(CEntityInstance* pEntity)
callback->Execute();
}
}
void CEntityListener::OnEntityDeleted(CEntityInstance* pEntity)
{
void CEntityListener::OnEntityDeleted(CEntityInstance *pEntity) {
auto callback = globals::entityManager.on_entity_deleted_callback;
if (callback && callback->GetFunctionCount()) {
@@ -92,8 +85,7 @@ void CEntityListener::OnEntityDeleted(CEntityInstance* pEntity)
callback->Execute();
}
}
void CEntityListener::OnEntityParentChanged(CEntityInstance* pEntity, CEntityInstance* pNewParent)
{
void CEntityListener::OnEntityParentChanged(CEntityInstance *pEntity, CEntityInstance *pNewParent) {
auto callback = globals::entityManager.on_entity_parent_changed_callback;
if (callback && callback->GetFunctionCount()) {
@@ -105,36 +97,35 @@ void CEntityListener::OnEntityParentChanged(CEntityInstance* pEntity, CEntityIns
}
void EntityManager::HookEntityOutput(const char* szClassname, const char* szOutput,
CallbackT fnCallback, HookMode mode)
CallbackT fnCallback)
{
auto outputKey = OutputKey_t(szClassname, szOutput);
CallbackPair* pCallbackPair;
counterstrikesharp::ScriptCallback* callback;
auto search = m_pHookMap.find(outputKey);
if (search == m_pHookMap.end()) {
m_pHookMap[outputKey] = new CallbackPair();
pCallbackPair = m_pHookMap[outputKey];
} else
pCallbackPair = search->second;
callback = globals::callbackManager.CreateCallback("");
m_pHookMap[outputKey] = callback;
}
else
callback = search->second;
auto* pCallback = mode == HookMode::Pre ? pCallbackPair->pre : pCallbackPair->post;
pCallback->AddListener(fnCallback);
callback->AddListener(fnCallback);
}
void EntityManager::UnhookEntityOutput(const char* szClassname, const char* szOutput,
CallbackT fnCallback, HookMode mode)
CallbackT fnCallback)
{
auto outputKey = OutputKey_t(szClassname, szOutput);
counterstrikesharp::ScriptCallback* callback;
auto search = m_pHookMap.find(outputKey);
if (search != m_pHookMap.end()) {
auto* pCallbackPair = search->second;
auto callback = search->second;
callback->RemoveListener(fnCallback);
auto* pCallback = mode == Pre ? pCallbackPair->pre : pCallbackPair->post;
pCallback->RemoveListener(fnCallback);
if (!pCallbackPair->HasCallbacks()) {
if (!callback->GetFunctionCount()) {
globals::callbackManager.ReleaseCallback(callback);
m_pHookMap.erase(outputKey);
}
}
@@ -143,80 +134,30 @@ void EntityManager::UnhookEntityOutput(const char* szClassname, const char* szOu
void DetourFireOutputInternal(CEntityIOOutput* const pThis, CEntityInstance* pActivator,
CEntityInstance* pCaller, const CVariant* const value, float flDelay)
{
std::vector vecSearchKeys{OutputKey_t("*", pThis->m_pDesc->m_pName),
OutputKey_t("*", "*")};
if (pCaller) {
vecSearchKeys.push_back(OutputKey_t(pCaller->GetClassname(), pThis->m_pDesc->m_pName));
OutputKey_t(pCaller->GetClassname(), "*");
}
std::vector<CallbackPair*> vecCallbackPairs;
if (pCaller) {
CSSHARP_CORE_TRACE("[EntityManager][FireOutputHook] - {}, {}", pThis->m_pDesc->m_pName,
pCaller->GetClassname());
auto outputKey = OutputKey_t(pCaller->GetClassname(), pThis->m_pDesc->m_pName);
auto& hookMap = globals::entityManager.m_pHookMap;
for (auto& searchKey : vecSearchKeys) {
auto search = hookMap.find(searchKey);
if (search != hookMap.end()) {
vecCallbackPairs.push_back(search->second);
auto search = hookMap.find(outputKey);
if (search != hookMap.end()) {
auto callback = search->second;
if (callback && callback->GetFunctionCount()) {
callback->ScriptContext().Reset();
callback->ScriptContext().Push(pThis->m_pDesc->m_pName);
callback->ScriptContext().Push(pActivator);
callback->ScriptContext().Push(pCaller);
callback->ScriptContext().Push(flDelay);
callback->Execute();
}
}
} else
CSSHARP_CORE_TRACE("[EntityManager][FireOutputHook] - {}, unknown caller",
pThis->m_pDesc->m_pName);
HookResult result = HookResult::Continue;
for (auto pCallbackPair : vecCallbackPairs) {
if (pCallbackPair->pre->GetFunctionCount()) {
pCallbackPair->pre->ScriptContext().Reset();
pCallbackPair->pre->ScriptContext().Push(pThis);
pCallbackPair->pre->ScriptContext().Push(pThis->m_pDesc->m_pName);
pCallbackPair->pre->ScriptContext().Push(pActivator);
pCallbackPair->pre->ScriptContext().Push(pCaller);
pCallbackPair->pre->ScriptContext().Push(value);
pCallbackPair->pre->ScriptContext().Push(flDelay);
for (auto fnMethodToCall : pCallbackPair->pre->GetFunctions()) {
if (!fnMethodToCall)
continue;
fnMethodToCall(&pCallbackPair->pre->ScriptContextStruct());
auto thisResult = pCallbackPair->pre->ScriptContext().GetResult<HookResult>();
if (thisResult >= HookResult::Stop) {
return;
}
if (thisResult > result) {
result = thisResult;
}
}
}
}
if (result >= HookResult::Handled) {
return;
}
CSSHARP_CORE_TRACE("[EntityManager][FireOutputHook] - {}, unknown caller", pThis->m_pDesc->m_pName);
m_pFireOutputInternal(pThis, pActivator, pCaller, value, flDelay);
for (auto pCallbackPair : vecCallbackPairs) {
if (pCallbackPair->post->GetFunctionCount()) {
pCallbackPair->post->ScriptContext().Reset();
pCallbackPair->post->ScriptContext().Push(pThis);
pCallbackPair->post->ScriptContext().Push(pThis->m_pDesc->m_pName);
pCallbackPair->post->ScriptContext().Push(pActivator);
pCallbackPair->post->ScriptContext().Push(pCaller);
pCallbackPair->post->ScriptContext().Push(value);
pCallbackPair->post->ScriptContext().Push(flDelay);
pCallbackPair->post->Execute();
}
}
}
} // namespace counterstrikesharp
} // namespace counterstrikesharp

View File

@@ -23,7 +23,6 @@
#include "core/global_listener.h"
#include "scripting/script_engine.h"
#include "entitysystem.h"
#include "scripting/callback_manager.h"
// variant.h depends on ivscript.h, lets not include the whole thing
DECLARE_POINTER_HANDLE(HSCRIPT);
@@ -49,10 +48,10 @@ public:
~EntityManager();
void OnAllInitialized() override;
void OnShutdown() override;
void HookEntityOutput(const char* szClassname, const char* szOutput, CallbackT fnCallback, HookMode mode);
void UnhookEntityOutput(const char* szClassname, const char* szOutput, CallbackT fnCallback, HookMode mode);
void HookEntityOutput(const char* szClassname, const char* szOutput, CallbackT fnCallback);
void UnhookEntityOutput(const char* szClassname, const char* szOutput, CallbackT fnCallback);
CEntityListener entityListener;
std::map<OutputKey_t, CallbackPair*> m_pHookMap;
std::map<OutputKey_t, counterstrikesharp::ScriptCallback*> m_pHookMap;
private:
ScriptCallback *on_entity_spawned_callback;
ScriptCallback *on_entity_created_callback;

View File

@@ -103,6 +103,7 @@ bool EventManager::HookEvent(const char* szName, CallbackT fnCallback, bool bPos
} else {
if (!pHook->m_pPreHook) {
pHook->m_pPreHook = globals::callbackManager.CreateCallback("");
;
}
pHook->m_pPreHook->AddListener(fnCallback);
@@ -129,18 +130,19 @@ bool EventManager::UnhookEvent(const char* szName, CallbackT fnCallback, bool bP
pCallback = pHook->m_pPreHook;
}
pCallback->RemoveListener(fnCallback);
if (pCallback->GetFunctionCount() == 0) {
globals::callbackManager.ReleaseCallback(pCallback);
if (bPost) {
pHook->m_pPostHook = nullptr;
} else {
pHook->m_pPreHook = nullptr;
}
// Remove from function list
if (pCallback == nullptr) {
return false;
}
if (bPost) {
pHook->m_pPostHook = nullptr;
} else {
pHook->m_pPreHook = nullptr;
}
// TODO: Clean up callback if theres noone left attached.
CSSHARP_CORE_TRACE("Unhooking event: {0} with callback pointer: {1}", szName, (void*)fnCallback);
return true;

View File

@@ -23,9 +23,6 @@ SH_DECL_HOOK1_void(ISource2Server, ServerHibernationUpdate, SH_NOATTRIB, 0, bool
SH_DECL_HOOK0_void(ISource2Server, GameServerSteamAPIActivated, SH_NOATTRIB, 0);
SH_DECL_HOOK0_void(ISource2Server, GameServerSteamAPIDeactivated, SH_NOATTRIB, 0);
SH_DECL_HOOK1_void(ISource2Server, OnHostNameChanged, SH_NOATTRIB, 0, const char*);
SH_DECL_HOOK0_void(ISource2Server, PreFatalShutdown, const, 0);
SH_DECL_HOOK1_void(ISource2Server, UpdateWhenNotInGame, SH_NOATTRIB, 0, float);
SH_DECL_HOOK1_void(ISource2Server, PreWorldUpdate, SH_NOATTRIB, 0, bool);
namespace counterstrikesharp {
@@ -42,20 +39,11 @@ void ServerManager::OnAllInitialized() {
SH_MEMBER(this, &ServerManager::GameServerSteamAPIDeactivated), true);
SH_ADD_HOOK(ISource2Server, OnHostNameChanged, globals::server,
SH_MEMBER(this, &ServerManager::OnHostNameChanged), true);
SH_ADD_HOOK(ISource2Server, PreFatalShutdown, globals::server,
SH_MEMBER(this, &ServerManager::PreFatalShutdown), true);
SH_ADD_HOOK(ISource2Server, UpdateWhenNotInGame, globals::server,
SH_MEMBER(this, &ServerManager::UpdateWhenNotInGame), true);
SH_ADD_HOOK(ISource2Server, PreWorldUpdate, globals::server,
SH_MEMBER(this, &ServerManager::PreWorldUpdate), true);
on_server_hibernation_update_callback = globals::callbackManager.CreateCallback("OnServerHibernationUpdate");
on_server_steam_api_activated_callback = globals::callbackManager.CreateCallback("OnGameServerSteamAPIActivated");
on_server_steam_api_deactivated_callback = globals::callbackManager.CreateCallback("OnGameServerSteamAPIDeactivated");
on_server_hostname_changed_callback = globals::callbackManager.CreateCallback("OnHostNameChanged");
on_server_pre_fatal_shutdown = globals::callbackManager.CreateCallback("OnPreFatalShutdown");
on_server_update_when_not_in_game = globals::callbackManager.CreateCallback("OnUpdateWhenNotInGame");
on_server_pre_world_update = globals::callbackManager.CreateCallback("OnServerPreWorldUpdate");
}
void ServerManager::OnShutdown() {
@@ -67,20 +55,11 @@ void ServerManager::OnShutdown() {
SH_MEMBER(this, &ServerManager::GameServerSteamAPIDeactivated), true);
SH_REMOVE_HOOK(ISource2Server, OnHostNameChanged, globals::server,
SH_MEMBER(this, &ServerManager::OnHostNameChanged), true);
SH_REMOVE_HOOK(ISource2Server, PreFatalShutdown, globals::server,
SH_MEMBER(this, &ServerManager::PreFatalShutdown), true);
SH_REMOVE_HOOK(ISource2Server, UpdateWhenNotInGame, globals::server,
SH_MEMBER(this, &ServerManager::UpdateWhenNotInGame), true);
SH_REMOVE_HOOK(ISource2Server, PreWorldUpdate, globals::server,
SH_MEMBER(this, &ServerManager::PreWorldUpdate), true);
globals::callbackManager.ReleaseCallback(on_server_hibernation_update_callback);
globals::callbackManager.ReleaseCallback(on_server_steam_api_activated_callback);
globals::callbackManager.ReleaseCallback(on_server_steam_api_deactivated_callback);
globals::callbackManager.ReleaseCallback(on_server_hostname_changed_callback);
globals::callbackManager.ReleaseCallback(on_server_pre_fatal_shutdown);
globals::callbackManager.ReleaseCallback(on_server_update_when_not_in_game);
globals::callbackManager.ReleaseCallback(on_server_pre_world_update);
}
void* ServerManager::GetEconItemSystem()
@@ -88,11 +67,6 @@ void* ServerManager::GetEconItemSystem()
return globals::server->GetEconItemSystem();
}
bool ServerManager::IsPaused()
{
return globals::server->IsPaused();
}
void ServerManager::ServerHibernationUpdate(bool bHibernating)
{
CSSHARP_CORE_TRACE("Server hibernation update {0}", bHibernating);
@@ -143,58 +117,4 @@ void ServerManager::OnHostNameChanged(const char *pHostname)
}
}
void ServerManager::PreFatalShutdown()
{
CSSHARP_CORE_TRACE("Pre fatal shutdown");
auto callback = globals::serverManager.on_server_pre_fatal_shutdown;
if (callback && callback->GetFunctionCount()) {
callback->ScriptContext().Reset();
callback->Execute();
}
}
void ServerManager::UpdateWhenNotInGame(float flFrameTime)
{
CSSHARP_CORE_TRACE("Update when not in game {}", flFrameTime);
auto callback = globals::serverManager.on_server_update_when_not_in_game;
if (callback && callback->GetFunctionCount()) {
callback->ScriptContext().Reset();
callback->ScriptContext().Push(flFrameTime);
callback->Execute();
}
}
void ServerManager::PreWorldUpdate(bool bSimulating)
{
std::lock_guard<std::mutex> lock(m_nextWorldUpdateTasksLock);
if (!m_nextWorldUpdateTasks.empty()) {
CSSHARP_CORE_TRACE("Executing queued tasks of size: {0} at time {1}", m_nextWorldUpdateTasks.size(),
globals::getGlobalVars()->curtime);
for (size_t i = 0; i < m_nextWorldUpdateTasks.size(); i++) {
m_nextWorldUpdateTasks[i]();
}
m_nextWorldUpdateTasks.clear();
}
auto callback = globals::serverManager.on_server_pre_world_update;
if (callback && callback->GetFunctionCount()) {
callback->ScriptContext().Reset();
callback->ScriptContext().Push(bSimulating);
callback->Execute();
}
}
void ServerManager::AddTaskForNextWorldUpdate(std::function<void()>&& task)
{
std::lock_guard<std::mutex> lock(m_nextWorldUpdateTasksLock);
m_nextWorldUpdateTasks.push_back(std::forward<decltype(task)>(task));
}
} // namespace counterstrikesharp

View File

@@ -30,29 +30,17 @@ public:
void OnAllInitialized() override;
void OnShutdown() override;
void* GetEconItemSystem();
bool IsPaused();
void AddTaskForNextWorldUpdate(std::function<void()> &&task);
private:
void ServerHibernationUpdate(bool bHibernating);
void GameServerSteamAPIActivated();
void GameServerSteamAPIDeactivated();
void OnHostNameChanged(const char *pHostname);
void PreFatalShutdown();
void UpdateWhenNotInGame(float flFrameTime);
void PreWorldUpdate(bool bSimulating);
ScriptCallback *on_server_hibernation_update_callback;
ScriptCallback *on_server_steam_api_activated_callback;
ScriptCallback *on_server_steam_api_deactivated_callback;
ScriptCallback *on_server_hostname_changed_callback;
ScriptCallback *on_server_pre_fatal_shutdown;
ScriptCallback *on_server_update_when_not_in_game;
ScriptCallback *on_server_pre_world_update;
std::vector<std::function<void()>> m_nextWorldUpdateTasks;
std::mutex m_nextWorldUpdateTasksLock;
};
} // namespace counterstrikesharp

View File

@@ -37,7 +37,7 @@ CModule::CModule(const char* path, const char* module) : m_pszModule(module), m_
void* CModule::FindSignature(const char* signature)
{
if (signature == nullptr || strlen(signature) == 0) {
if (strlen(signature) == 0) {
return nullptr;
}

View File

@@ -41,17 +41,6 @@ DLL_EXPORT void InvokeNative(counterstrikesharp::fxNativeContext& context)
if (context.nativeIdentifier == 0)
return;
if (context.nativeIdentifier != counterstrikesharp::hash_string_const("QUEUE_TASK_FOR_NEXT_FRAME") &&
context.nativeIdentifier != counterstrikesharp::hash_string_const("QUEUE_TASK_FOR_NEXT_WORLD_UPDATE") &&
counterstrikesharp::globals::gameThreadId != std::this_thread::get_id())
{
counterstrikesharp::ScriptContextRaw scriptContext(context);
scriptContext.ThrowNativeError("Invoked on a non-main thread");
CSSHARP_CORE_CRITICAL("Native {:x} was invoked on a non-main thread", context.nativeIdentifier);
return;
}
counterstrikesharp::ScriptEngine::InvokeNative(context);
}
@@ -78,7 +67,6 @@ bool CounterStrikeSharpMMPlugin::Load(PluginId id, ISmmAPI* ismm, char* error, s
{
PLUGIN_SAVEVARS();
globals::ismm = ismm;
globals::gameThreadId = std::this_thread::get_id();
Log::Init();
@@ -176,8 +164,6 @@ void CounterStrikeSharpMMPlugin::AllPluginsLoaded()
void CounterStrikeSharpMMPlugin::AddTaskForNextFrame(std::function<void()>&& task)
{
std::lock_guard<std::mutex> lock(m_nextTasksLock);
m_nextTasks.push_back(std::forward<decltype(task)>(task));
}
@@ -191,8 +177,6 @@ void CounterStrikeSharpMMPlugin::Hook_GameFrame(bool simulating, bool bFirstTick
*/
globals::timerSystem.OnGameFrame(simulating);
std::lock_guard<std::mutex> lock(m_nextTasksLock);
if (m_nextTasks.empty())
return;
@@ -241,4 +225,4 @@ const char* CounterStrikeSharpMMPlugin::GetURL()
{
return "https://github.com/roflmuffin/CounterStrikeSharp";
}
} // namespace counterstrikesharp
} // namespace counterstrikesharp

View File

@@ -61,7 +61,6 @@ public:
private:
std::vector<std::function<void()>> m_nextTasks;
std::mutex m_nextTasksLock;
};
static ScriptCallback *on_activate_callback;

View File

@@ -118,24 +118,4 @@ void CallbackManager::PrintCallbackDebug()
CSSHARP_CORE_INFO("{0} ({0})\n", pCallback->GetName().c_str(), 1);
}
}
CallbackPair::CallbackPair()
{
pre = globals::callbackManager.CreateCallback("");
post = globals::callbackManager.CreateCallback("");
}
CallbackPair::CallbackPair(bool bNoCallbacks)
{
if (!bNoCallbacks) {
pre = globals::callbackManager.CreateCallback("");
post = globals::callbackManager.CreateCallback("");
}
}
CallbackPair::~CallbackPair()
{
globals::callbackManager.ReleaseCallback(pre);
globals::callbackManager.ReleaseCallback(post);
}
} // namespace counterstrikesharp

View File

@@ -35,6 +35,7 @@ class ScriptCallback
unsigned int GetFunctionCount() { return m_functions.size(); }
std::vector<CallbackT> GetFunctions() { return m_functions; }
void Execute(bool bResetContext = true);
void Reset();
ScriptContextRaw& ScriptContext() { return m_script_context_raw; }
@@ -63,18 +64,4 @@ class CallbackManager : public GlobalClass
std::vector<ScriptCallback*> m_managed;
};
class CallbackPair
{
public:
CallbackPair();
CallbackPair(bool bNoCallbacks);
~CallbackPair();
bool HasCallbacks() const
{ return pre->GetFunctionCount() > 0 || post->GetFunctionCount() > 0; }
public:
ScriptCallback* pre;
ScriptCallback* post;
};
} // namespace counterstrikesharp

View File

@@ -1,7 +1,4 @@
OnServerHibernationUpdate: isHibernating:bool
OnGameServerSteamAPIActivated:
OnGameServerSteamAPIDeactivated:
OnHostNameChanged: hostname:string
PreFatalShutdown:
UpdateWhenNotInGame: frameTime:float
PreWorldUpdate: simulating:bool
OnHostNameChanged: hostname:string

View File

@@ -124,23 +124,6 @@ static void IssueClientCommand(ScriptContext& script_context)
globals::engine->ClientCommand(CPlayerSlot(entity_index), command);
}
static const char* GetClientConVarValue(ScriptContext& script_context)
{
auto playerSlot = script_context.GetArgument<int>(0);
auto convarName = script_context.GetArgument<const char*>(1);
return globals::engine->GetClientConVarValue(CPlayerSlot(playerSlot), convarName);
}
static void SetFakeClientConVarValue(ScriptContext& script_context)
{
auto playerSlot = script_context.GetArgument<int>(0);
auto convarName = script_context.GetArgument<const char*>(1);
auto convarValue = script_context.GetArgument<const char*>(2);
globals::engine->SetFakeClientConVarValue(CPlayerSlot(playerSlot), convarName, convarValue);
}
ConVar* FindConVar(ScriptContext& script_context)
{
auto name = script_context.GetArgument<const char*>(0);
@@ -180,7 +163,5 @@ REGISTER_NATIVES(commands, {
ScriptEngine::RegisterNativeHandler("SET_CONVAR_STRING_VALUE", SetConVarStringValue);
ScriptEngine::RegisterNativeHandler("ISSUE_CLIENT_COMMAND", IssueClientCommand);
ScriptEngine::RegisterNativeHandler("GET_CLIENT_CONVAR_VALUE", GetClientConVarValue);
ScriptEngine::RegisterNativeHandler("SET_FAKE_CLIENT_CONVAR_VALUE", SetFakeClientConVarValue);
})
} // namespace counterstrikesharp

View File

@@ -8,6 +8,4 @@ COMMAND_GET_COMMAND_STRING: command:pointer -> string
COMMAND_GET_ARG_BY_INDEX: command:pointer,index:int -> string
ISSUE_CLIENT_COMMAND: clientIndex:int,command:string -> void
FIND_CONVAR: name:string -> pointer
SET_CONVAR_STRING_VALUE: convar:pointer,value:string -> void
GET_CLIENT_CONVAR_VALUE: clientIndex:int,convarName:string -> string
SET_FAKE_CLIENT_CONVAR_VALUE: clientIndex:int,convarName:string,convarValue:string -> void
SET_CONVAR_STRING_VALUE: convar:pointer,value:string -> void

View File

@@ -30,7 +30,6 @@
#include "core/memory.h"
#include "core/log.h"
#include "core/function.h"
#include "core/managers/server_manager.h"
// clang-format on
#if _WIN32
@@ -218,14 +217,6 @@ void QueueTaskForNextFrame(ScriptContext& script_context)
globals::mmPlugin->AddTaskForNextFrame([func]() { reinterpret_cast<voidfunc*>(func)(); });
}
void QueueTaskForNextWorldUpdate(ScriptContext& script_context)
{
auto func = script_context.GetArgument<void*>(0);
typedef void(voidfunc)(void);
globals::serverManager.AddTaskForNextWorldUpdate([func]() { reinterpret_cast<voidfunc*>(func)(); });
}
enum InterfaceType
{
Engine,
@@ -317,7 +308,6 @@ REGISTER_NATIVES(engine, {
ScriptEngine::RegisterNativeHandler("TRACE_RAY", TraceRay);
ScriptEngine::RegisterNativeHandler("GET_TICKED_TIME", GetTickedTime);
ScriptEngine::RegisterNativeHandler("QUEUE_TASK_FOR_NEXT_FRAME", QueueTaskForNextFrame);
ScriptEngine::RegisterNativeHandler("QUEUE_TASK_FOR_NEXT_WORLD_UPDATE", QueueTaskForNextWorldUpdate);
ScriptEngine::RegisterNativeHandler("GET_VALVE_INTERFACE", GetValveInterface);
ScriptEngine::RegisterNativeHandler("GET_COMMAND_PARAM_VALUE", GetCommandParamValue);
ScriptEngine::RegisterNativeHandler("PRINT_TO_SERVER_CONSOLE", PrintToServerConsole);

View File

@@ -21,7 +21,6 @@ TRACE_FILTER_PROXY_SET_SHOULD_HIT_ENTITY_CALLBACK: trace_filter:pointer, callbac
NEW_TRACE_RESULT: -> pointer
GET_TICKED_TIME: -> double
QUEUE_TASK_FOR_NEXT_FRAME: callback:pointer -> void
QUEUE_TASK_FOR_NEXT_WORLD_UPDATE: callback:pointer -> void
GET_VALVE_INTERFACE: interfaceType:int, interfaceName:string -> pointer
GET_COMMAND_PARAM_VALUE: param:string, dataType:DataType_t, defaultValue:any -> any
PRINT_TO_SERVER_CONSOLE: msg:string -> void

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