【写在前面】

写C++的同志并且想搞这个服务器插件的,我觉得您得看一看这篇文章。

原帖地址:本帖非搬运,原帖可能已经失效,原作者就是我

最后,请不要介意本文屎一样的排版

使用Source SDK编写Source引擎服务器的插件(L4D2 Version)

在此之前,如果还没搞明白metamod,sourcemod,source SDK三者之间联系的,请先看这个帖子(这个帖子反正已经凉了,但里面有些“干货”可以拿来看看)

本篇文章意旨您可以使用C++来编写您服务器的插件,据我所知,2013年之后很少有人再提起C++编写此引擎插件的事情了(现在2018了),取而代之的是metamod与Sourcemod(现在甚至编写metamod上面插件的人都很少了),国外基本也变成这样,既然如此,为什么我还要在此提出C++的使用呢?原因很简单:

自由超多人数服务器的性能

可以以这个插件作为学习C++的铺垫,为插件做一个HelloWorld,同时可以增强自身C++的功底。

pawn语言比较冷门,Source pawn更加不用说了,学精了也只能用在这些特定的地方,而C++可以跨平台干出任何想要的事情。

目测可以与exe一样达到防止轻易被反编译。

为什么自由,知道指针与内嵌汇编的人都知道它的厉害之处,而且需要一个厉害的人去驾驭它,但是这种人的孩子估计都上高中了,这也是C++插件基本绝种的主要原因(看看metamod社区多么冷清),第二,SourcePawn的性能在人数少的时候自然没啥可体现,但人多起来的时候,这个时候非常考验服务器性能(因为不优化插件,你就只能增加服务器性能,你得花钱啊!)。毕竟Sourcemod只是一个接口,如果接口性能考虑不完全,势必导致在接口之上编写的SourcePawn的性能大大降低。

我说了这么多,我并不是讨厌SourcePawn,而是因为SourcePawn限制了我的想象力,如果说把C++看成是宇宙,那么SourcePawn就是地球,这个概念想必都懂,我也就不废话了,进入正题:

想完成从源代码到插件的编写,你需要几样东西:(以下伸手党福利)

visual studio 2008(最好是express版本)
这是什么链接我就不说了,自己拿某雷去下,浏览器下载比较慢的

hl2-sdk-l4d2
可以说是求生2的源代码

求生之路2的scrds服务器
这个的话,反正我是官方正版的,可以去steam客户端的工具里面下载(图个情怀,现在谁还不买正版玩,我给个鄙视)

准备好这些,那就开始吧,准备工作挺简单的:

先用vs2008创建一个项目,你可以不创建在桌面上,但是创建之后别忘记在哪个目录。(我同学经常犯这毛病)

单击【文件】->【新建】->【项目】,确保选择Win32 项目,名称随便输,位置自己定(还是那句话,创建之后别忘记在哪个目录),然后选择空项目(这个很有必要)然后就创建成功了,而且务必选中DLL,不然后期会很难搞,都是点点鼠标的事儿,我就不放图了。

拷贝头文件与库至工程目录

将你的下载的hl2-sdk-l4d2的zip包解包,然后你能看见一大堆玩意儿,【choreoobjects, common, devtools, game, 等等】,然而你只需要其中的三个目录,game, lib, 与public。将这三个文件夹复制到你的工程目录(哈?你说目录不知道是啥?我刚才强调了两遍的话白说了吗?),拷贝完了以后,假如你如果想钻研一下Source的源码的,你可以看看hl2-sdk-l4d2 zip包下的其他目录,不想钻研的,SDK留着也没用了,直接删了便好。

设置工程,这个过程比较麻烦,但是弄好就一劳永逸,涉及编译原理的东西我也不说了,清楚的知道我在做什么,不清楚的你照做便罢。

首先你要为你的工程添加一个源文件,源文件名自己取,最好不要用中文,不这么做而直接跳过做下面这步的话,我已经能想象到你满头问号的表情了,创建完先不用动,看下一步。

创建好功成之后的vs2008的主界面的左上角应该有一个树形列表,写着 解决方案“项目名”、“项目名”, 头文件,源文件, 资源文件,右键那个项目名(解决方案下面那个),再点击属性,在弹出的框里你应该能找到c/c++这一个标签(上面一步没有做,这一步你找不到这个标签的),点击它,在右边应该能找到包含附加目录,在里面填写以下内容:public;public\tier0;public\tier1;public\tier2;public\tier3;game;game\shared;game\server;game\client然后展开链接器标签,点击常规,在附加库目录里填上lib\public,再点击输入,分别在附加依赖项和忽略特定库里添加mathlib.lib tier0.lib tier1.lib tier2.lib vstdlib.lib和libc;libcd;libcmt,至此,项目设置应该是完成了,其他东西也没必要动了。

编写源代码,挂接IServerPluginCallbacks接口实现并且暴露自身某块以供服务器调用。

说的很好听,不过就是继承一下IServerPluginCallbacks再写个宏就完事儿了。代码我就放这儿吧。(懒得上传文件)

这段代码我没有测试过,这是我从其他工程提取出来的一个纯的代码, 代码编译错误请留言,我会尽力帮忙看的。

编译好了,进工程目录找到编译好的dll,放到桌面上,以便下一步使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#define GAME_DLL
#include <stdio.h>
#include "interface.h"
#include "filesystem.h"
#include "engine/iserverplugin.h"
#include "game/server/iplayerinfo.h"
#include "eiface.h"
#include "igameevents.h"
#include "convar.h"
#include "Color.h"
#include "vstdlib/random.h"
#include "engine/IEngineTrace.h"
#include "tier2/tier2.h"
#include "shareddefs.h"
#include "tier0/memdbgon.h"

class CEmptyServerPlugin: public IServerPluginCallbacks
{
public:
CEmptyServerPlugin();
~CEmptyServerPlugin();

// IServerPluginCallbacks methods
virtual bool Load( CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory );
virtual void Unload( void );
virtual void Pause( void );
virtual void UnPause( void );
virtual const char *GetPluginDescription( void );
virtual void LevelInit( char const *pMapName );
virtual void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax );
virtual void GameFrame( bool simulating );
virtual void LevelShutdown( void );
virtual void ClientActive( edict_t *pEntity );
virtual void ClientDisconnect( edict_t *pEntity );
virtual void ClientPutInServer( edict_t *pEntity, char const *playername );
virtual void SetCommandClient( int index );
virtual void ClientSettingsChanged( edict_t *pEdict );
virtual PLUGIN_RESULT ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen );
virtual PLUGIN_RESULT ClientCommand( edict_t *pEntity, const CCommand &args );
virtual PLUGIN_RESULT NetworkIDValidated( const char *pszUserName, const char *pszNetworkID );
virtual void OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue );

};


//
// The plugin is a static singleton that is exported as an interface
//
CEmptyServerPlugin g_EmtpyServerPlugin;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CEmptyServerPlugin, IServerPluginCallbacks, INTERFACEVERSION_ISERVERPLUGINCALLBACKS, g_EmtpyServerPlugin );

//---------------------------------------------------------------------------------
// Purpose: constructor/destructor
//---------------------------------------------------------------------------------
CEmptyServerPlugin::CEmptyServerPlugin()
{
}

CEmptyServerPlugin::~CEmptyServerPlugin()
{
}

//---------------------------------------------------------------------------------
// Purpose: called when the plugin is loaded, load the interface we need from the engine
//---------------------------------------------------------------------------------
bool CEmptyServerPlugin::Load( CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory )
{
return true;
}

//---------------------------------------------------------------------------------
// Purpose: called when the plugin is unloaded (turned off)
//---------------------------------------------------------------------------------
void CEmptyServerPlugin::Unload( void )
{
}

//---------------------------------------------------------------------------------
// Purpose: called when the plugin is paused (i.e should stop running but isn't unloaded)
//---------------------------------------------------------------------------------
void CEmptyServerPlugin::Pause( void )
{
}

//---------------------------------------------------------------------------------
// Purpose: called when the plugin is unpaused (i.e should start executing again)
//---------------------------------------------------------------------------------
void CEmptyServerPlugin::UnPause( void )
{
}

//---------------------------------------------------------------------------------
// Purpose: the name of this plugin, returned in "plugin_print" command
//---------------------------------------------------------------------------------
const char *CEmptyServerPlugin::GetPluginDescription( void )
{
return "Plugin Made by Yourself";
}

//---------------------------------------------------------------------------------
// Purpose: called on level start
//---------------------------------------------------------------------------------
void CEmptyServerPlugin::LevelInit( char const *pMapName )
{

}

//---------------------------------------------------------------------------------
// Purpose: called on level start, when the server is ready to accept client connections
// edictCount is the number of entities in the level, clientMax is the max client count
//---------------------------------------------------------------------------------
void CEmptyServerPlugin::ServerActivate( edict_t *pEdictList, int edictCount, int clientMax )
{

}

//---------------------------------------------------------------------------------
// Purpose: called once per server frame, do recurring work here (like checking for timeouts)
//---------------------------------------------------------------------------------
void CEmptyServerPlugin::GameFrame( bool simulating )
{
}

//---------------------------------------------------------------------------------
// Purpose: called on level end (as the server is shutting down or going to a new map)
//---------------------------------------------------------------------------------
void CEmptyServerPlugin::LevelShutdown( void ) // !!!!this can get called multiple times per map change
{

}

//---------------------------------------------------------------------------------
// Purpose: called when a client spawns into a server (i.e as they begin to play)
//---------------------------------------------------------------------------------
void CEmptyServerPlugin::ClientActive( edict_t *pEntity )
{

}

//---------------------------------------------------------------------------------
// Purpose: called when a client leaves a server (or is timed out)
//---------------------------------------------------------------------------------
void CEmptyServerPlugin::ClientDisconnect( edict_t *pEntity )
{
}

void CEmptyServerPlugin::OnQueryCvarValueFinished( QueryCvarCookie_t iCookie,
edict_t *pPlayerEntity,
EQueryCvarValueStatus eStatus,
const char *pCvarName,
const char *pCvarValue )
{
}

void CEmptyServerPlugin::ClientPutInServer(edict_t* pEntity, const char* playername)
{
}

void CEmptyServerPlugin::SetCommandClient(int index)
{
}

void CEmptyServerPlugin::ClientSettingsChanged(edict_t* pEdict)
{
}

PLUGIN_RESULT CEmptyServerPlugin::ClientConnect(bool* bAllowConnect, edict_t* pEntity, const char* pszName, const char* pszAddress, char* reject, int maxrejectlen)
{
return PLUGIN_CONTINUE;
}

PLUGIN_RESULT CEmptyServerPlugin::ClientCommand(edict_t* pEntity, const CCommand& args)
{
return PLUGIN_CONTINUE;
}

PLUGIN_RESULT CEmptyServerPlugin::NetworkIDValidated(const char* pszUserName, const char* pszNetworkID)
{
return PLUGIN_CONTINUE;
}

【广告推销时间】我有一个写MC插件的教程,我详细介绍了插件的涵义与含义,以及插件是怎么插进服务器的,与MC服务器相同的是,L4D2服务器也有一个插件引导文件,后缀名为vdf,可以新建一个vdf文件(非中文名),然后放到addons文件夹(这个读者应该清楚吧),在vdf里面写上以下内容:

这段代码我建议手打,而且最好用notepad++编辑,不然服务器识别不出来就完蛋,而且缩进最好用tab键。(我是基于metamod包里面自带的那个vdf文件修改的)

Bash

“Plugin”
{
“file” “这里填写你的插件的目录,如果你dll文件放在addons目录里面,那么这里你可以写addons/文件名(不带.dll),注意,最外边两个引号你得留着”
}

运行服务器查看插件运行状态

没意外的情况下,服务器应该能运行起来了,这个时候在控制台输入plugin_print,应该可以看到插件了(如果只有两条杠,杠之间啥也没有,那你失败了,重新来过),或者你也可以留言寻求帮助(当然你得问明白),这个只是一个什么也没有做的空插件,我会在这个论坛写一些插件供参考。

尾声

到这儿成功的朋友和我击个掌吧!(挺不容易的),因为这个时候你已经游于自由的海洋之中了,我可以毫不负责任的讲,现在你啥都可以做,你可以在cpp文件里引用windows.h然后为你的插件创建一个窗口,窗口里调用d3d功能然后模拟一个客户端绘制并且监控当前服务器内正在发生画面(这个可以做到,当然我不会做,因为需要算法的知识),还可以与酷Q机器人进行对接,不需要数据库,实现QQ群内输入指令检查当前服务器的人数和所有玩家的名称,能想到并且能做到的事情太多了,还能够把代码交给单片机集群去处理(相信我,能实现),甚至还能够与MC服务器进行数据对接,非常神奇,然而这一切的一切,不过就是从一个小小的HelloWorld与一份浇不灭的信心,相信想钻的读者一定能够成功,最后,自由万岁