CS2サーバープラグイン開発:CSSharpがアツい
CS2のプラグインシステムについて。
MetaMod:Sourceを基盤とするのは変わりなく、その延長線上にさまざまなシステムが用意されているが、CSSharpという名前のプラグインシステムがとても良さそうなので調査。
公式ドキュメントページが以下。
開発が行われているレポジトリがこちら。
以下はドキュメントページに書かれているサンプルコード。動くかは知らない。
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 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");
}
}
C#、Unityのプラグインで軽く書いたのと、他人のコード軽く見たくらいしか経験がないけど、ぱっと見で何の処理をしているかわかるし、簡潔だしとても良さそう。
外部ライブラリとか使えるのだろうか...? というか、どうやってMetaModに渡しているかとかすごく気になる。
調べたいこと:
- CSSharpのインストール手順
- 外部のライブラリを使えるのか(主にHTTP, JSON処理など)
- 制限事項はあるのか
- CSSharpで作られたプラグインのコードリーディング(主にshobhit-pathak/MatchZy)
- 既存のSourceModプラグインのCSSharp移植
とりあえずローカルのDocker環境にCSSharpの試合サーバーを作成してみる。
イメージはcm2network/cs2 を使用。
以下、サーバーのコンテナ作成に使用したコマンド。
docker run -d -p 27015-27030/tcp -p 27015-27030/udp \
-v $(pwd)/cs2-data:/home/steam/cs2-dedicated/ \
-e DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true \
--name=cs2-dedicated-cssharp -e STEAMUSER=anonymous cm2network/cs2
MetaMod, CSSharpは、以下リンクを参考に $(pwd)/cs2-data 以下に手動でインストールする。
余談だが、CS:GOやCS:Sのプラグインが活発に開発されていた時代はDockerなんてなかったので、便利なものである。
無事CSSharpが読み込めた。
試しに、ドキュメント通りにHelloWorldプラグインを作成し、サーバーにロードしてみる。
適当なディレクトリを作成し、以下コマンドを実行。
dotnet new classlib --name HelloWorldPlugin
cd HelloWorldPlugin
dotnet add package CounterStrikeSharp.API
Class1.cs ファイルを以下のように編集してみる。
using CounterStrikeSharp.API.Core;
namespace HelloWorldPlugin;
public class HelloWorldPlugin : BasePlugin
{
public override string ModuleName => "C# Hello World Plugin - Flowing";
public override string ModuleVersion => "0.0.1";
public override void Load(bool hotReload)
{
Console.WriteLine("Hello World from CSSharp!");
}
}
以下コマンドでビルド。
dotnet build
bin/Debug/net7.0 に成果物が吐き出される。
CS2サーバーに実際にプラグインを導入する。
今回作成されたdllファイルの名前はHelloWorldPlugin.dll なので、game/csgo/addons/counterstrikesharp/plugins にHelloWorldPlugin という名前のディレクトリを作成し、CounterStrikeSharp.API.dll
ファイル以外のデータをコピーする。
ちなみに、自分の環境ではそもそもCounterStrikeSharp.API.dll
というファイルは存在しなかった。
作成した試合サーバーのコンテナをリロードする。
プラグインが読み込まれた。
ちなみに、試合サーバーはDocker(WSL2+Docker)、プラグインのコンパイルを実施したのはホスト機(Windows10+PowerShell)だが、問題なくコンパイル・読み込みが出来た。
また、地味にホットリロードに対応しているらしい。
dllファイルを置き換えると、Load
メソッドの引数のhotReload
にtrue渡された状態で実行される。
技術の進歩とDXの発達を感じる。
取り急ぎVSCodeで開発していたが、正しく設定が出来ていれば問題なく開発が進めれそうだ。
次はカスタムコマンドの実装や、ゲーム中のイベントに応じて発火するイベントフックの研究を行う。
サンプルコードを参考に、カスタムコマンド登録やイベントリスナーを登録しようとしたが、エラーが吐き出されてしまったので、ここからVisual Studio 2022に切り替える。
usingの追加などが必要だった。
追記。ここでVisual Studio 2022に切り替えたが、結局VSCodeに戻した。
開発経験は特に変わらない気がする。
滅茶苦茶エラーログ出たが、プレイヤーの接続イベントで発火するメソッドや、カスタムコマンドで発火するイベントの実装が出来た。
メソッド名はあくまで参考に使われる?ようで、GameEvent アトリビュートと、@event 引数の型が優先されて登録されるっぽい。
試しに、RCONチックな動作を実現するコマンドを実装してみた。
[ConsoleCommand("spdg_rcon", "Execute rcon command")]
[CommandHelper(minArgs: 1, usage: "[target]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSPDGRconCommand(CCSPlayerController? caller, CommandInfo command)
{
Server.ExecuteCommand(command.ArgByIndex(1));
}
HTTPで外部からGETしてきて、コンソールに表示するコマンドを作ろうとしたが、レスポンスが空文字になっていてうまく取得出来ていないようだった。
[ConsoleCommand("spdg_get_todos", "Get TODO List")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSPDGGetTodos(CCSPlayerController? caller, CommandInfo command)
{
using (var client = new HttpClient())
{
var path = "https://www.google.com/";
var response = client.GetAsync(path).GetAwaiter().GetResult();
var responseContent = response.Content;
var result = responseContent.ReadAsStringAsync().GetAwaiter().GetResult();
Logger.LogInformation("Got response: {}", result);
}
}
書き方を変えたら行けた。何が悪かったのかはまだわかってない。
[ConsoleCommand("spdg_get_todos", "Get TODO List")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSPDGGetTodos(CCSPlayerController? caller, CommandInfo command)
{
var client = new HttpClient();
var webRequest = new HttpRequestMessage(HttpMethod.Get, "https://jsonplaceholder.typicode.com/todos/1");
var response = client.Send(webRequest);
var reader = new StreamReader(response.Content.ReadAsStream());
var content = reader.ReadToEnd();
Logger.LogInformation("Received content: {content}", content);
}
ある程度調査が終わったので、CSSharp調査としてのこのスクラップはクローズする。
所感として、デファクトであったSourceModのプラグイン開発よりも数倍開発体験が良く、かつSourcePawnのような独自言語でもなく、標準的なC#を使って開発出来るのはかなりありがたい。
別スクラップにて、CSGO/SourceMod時代に存在したプラグインの移植と、新規マッチ管理プラグインの開発を行おうと思う。
(目標としては、get5loaderが死んでしまってかなり悲しいのでget5互換のような、HTTP経由で外部と連携することが念頭に置かれて開発されているマッチ管理システムが作りたい)
あまり触れてなかったけど、SourcrPawn時代は同じsourcepawnかC++の extensionしか参照出来なくて拡張性と資産の再活用がマジで終わってたんだけど、C#な上に他のライブラリも問題なく読み込めるので、外部ライブラリ活用して色々弄ったり、gRPCとか使ってプラグイン組めるのは非常に楽しみ。
よくわからんもの作りたい。