NetMessage 是 CS2 网络通信使用的一系列数据包,本质通过 Protobuf 实现。
准确的来说,UserMessage 是 NetMessage 中的一类 Message,在 usermessages.proto 文件中定义。
从内容看,UserMessage 主要负责游戏性的网络通信(例如屏幕抖动,声音,文字提示等)。
警告
在 CounterStrikeSharp 中,NetMessage 和 UserMessage 都使用 UserMessage 类表示,这意味着所有 NetMessage 都可以被 CounterStrikeSharp 处理。
CS2的全 Proto 文件仓库:https://github.com/SteamDatabase/GameTracking-CS2
虽然目前的 CounterStrikeSharp 无法做到与 Protobuf 对象直接交互,但它提供了 UserMessage 对象,其本质是一个 CNetMessagePB 对象(基本就是一个 Protobuf 对象),但是只能对其进行一些基础的读写操作。
你可以使用两种方式来构造一个 UserMessage 对象。
UserMessage.FromId(messageId);
创建一个 对应 id messageId 的 UserMessage 对象,具体 id 可以在 Proto 文件中获得。
样例:
创建一个 UM_Shake (120) UserMessage,其结构为 CUserMessageShake。
UserMessage.FromPartialName(partialName);
寻找名称含有 partialName 的 NetMessage 并创建一个 UserMessage 对象,具体名称可以在 Proto 文件中获得。
样例:
UserMessage.FromPartialName("SayText2");
创建一个 UM_SayText2 (118) UserMessage,其结构为 CUserMessageSayText2。
你可以使用以下示例代码在 CounterStrikeSharp 中读取一个 UserMessage 的字段:
UserMessage userMessage; // 你的 userMessage
bool f1 = userMessage.ReadBool(fieldName, index); // 读取 bool 类型
double f2 = userMessage.ReadDouble(fieldName, index); // 读取 double 类型
float f3 = userMessage.ReadFloat(fieldName, index); // 读取 float 类型
int f4 = userMessage.ReadInt(fieldName, index); // 读取 int 类型
long f5 = userMessage.ReadInt64(fieldName, index); // 读取 long 类型
uint f6 = userMessage.ReadUInt(fieldName, index); // 读取 uint 类型
ulong f7 = userMessage.ReadUInt64(fieldName, index); // 读取 ulong 类型
string f8 = userMessage.ReadString(fieldName, index); // 读取 string 类型
其中 fieldName 是字段名称,index 是一个可留空的参数,读取 repeated 字段时需要传入,代表索引。
警告
由于技术限制,你目前只能在 CounterStrikeSharp 中操作以上基础类型,不支持嵌套复杂类型。
你可以使用以下示例代码在 CounterStrikeSharp 中写入一个 UserMessage 的字段。
如果字段是单个值:
UserMessage userMessage; // 你的 userMessage
userMessage.SetBool(fieldName, value); // 写入 bool 类型
userMessage.SetDouble(fieldName, value); // 写入 double 类型
userMessage.SetFloat(fieldName, value); // 写入 float 类型
userMessage.SetInt(fieldName, value); // 写入 int 类型
userMessage.SetInt64(fieldName, value); // 写入 long 类型
userMessage.SetUInt(fieldName, value); // 写入 uint 类型
userMessage.SetUInt64(fieldName, value); // 写入 ulong 类型
userMessage.SetString(fieldName, value); // 写入 string 类型
如果字段是 repeated 字段:
UserMessage userMessage; // 你的 userMessage
userMessage.AddBool(fieldName, value); // 添加 bool 类型
userMessage.AddDouble(fieldName, value); // 添加 double 类型
userMessage.AddFloat(fieldName, value); // 添加 float 类型
userMessage.AddInt(fieldName, value); // 添加 int 类型
userMessage.AddInt64(fieldName, value); // 添加 long 类型
userMessage.AddUInt(fieldName, value); // 添加 uint 类型
userMessage.AddUInt64(fieldName, value); // 添加 ulong 类型
userMessage.AddString(fieldName, value); // 添加 string 类型
其中 fieldName 是字段名称。
警告
由于技术限制,你目前只能在 CounterStrikeSharp 中操作以上基础类型,不支持嵌套复杂类型。
Recipients 是 UserMessage 的接收者列表,代表一个 UserMessage 可以发送给哪些玩家,你可以使用以下示例代码来设置 Recipients:
UserMessage userMessage; // 你的 userMessage
userMessage.Recipients.Add(playerController); // 将一个玩家添加到接收者列表
userMessage.Recipients.Remove(playerController); // 将一个玩家从接收者列表移除
userMessage.Recipients.AddAllPlayers(); // 添加所有玩家
userMessage.Recipients.Clear(); // 删除所有玩家
你可以使用以下示例代码在 CounterStrikeSharp 中注册一个 NetMessage 监听器:
HookUserMessage(messageId, (userMessage) => {
return HookResult.Continue;
}, HookMode.Pre);
参数解释:
messageId: NetMessage 或 UserMessage 的 id, 可以在上文中的 Proto 文件中获得
userMessage: UserMessage 对象
HookMode: Hook 模式
可以看到,监听 NetMessage 的方式和 Hook 函数基本是一样的。
警告
截至 2024 年 12 月 11 日,CounterStrikeSharp 仅支持监听从服务器发至客户端的 NetMessage,而不支持客户端发送至服务器的 NetMessage。
你可以使用以下示例代码在 CounterStrikeSharp 中主动发送一个 NetMessage:
UserMessage userMessage = UserMessage.FromId(messageId); // 准备你的 UserMessage
userMessage.SetBool(fieldName, value); // 写入需要的字段
userMessage.Recipients.AddAllPlayers(); // 添加所有玩家
userMessage.Send(); // 发送
// 208 : GE_SosStartSoundEvent
HookUserMessage(208, (userMessage) => {
int entityIndex = userMessage.ReadInt("source_entity_index");
CEntityInstance? entity = Utilities.GetEntityFromIndex<CEntityInstance>(index);
if (entity?.DesignerName == "point_soundevent")
{
userMessage.Recipients.Clear();
}
return HookResult.Continue;
}, HookMode.Pre);
var userMessage = UserMessage.FromPartialName("Shake");
userMessage.SetFloat("duration", 2);
userMessage.SetFloat("amplitude", 5);
userMessage.SetFloat("frequency", 10f);
userMessage.SetInt("command", 0);
userMessage.Recipients.Add(player);
userMessage.Send();