本帖最后由 吔 ! 于 2020-10-7 13:31 编辑
该帖在此处有最新的更新,去掉了一些迷惑词语与中二片段
【写在前面】
写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再写个宏就完事儿了。代码我就放这儿吧。(懒得上传文件) - #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;
- }
复制代码这段代码我没有测试过,这是我从其他工程提取出来的一个纯的代码, 代码编译错误请留言,我会尽力帮忙看的。 编译好了,进工程目录找到编译好的dll,放到桌面上,以便下一步使用。 - 【广告推销时间】我有一个写MC插件的教程,我详细介绍了插件的涵义与含义,以及插件是怎么插进服务器的,与MC服务器相同的是,L4D2服务器也有一个插件引导文件,后缀名为vdf,可以新建一个vdf文件(非中文名),然后放到addons文件夹(这个读者应该清楚吧),在vdf里面写上以下内容:
- "Plugin"
- {
- "file" "这里填写你的插件的目录,如果你dll文件放在addons目录里面,那么这里你可以写addons/文件名(不带.dll),注意,最外边两个引号你得留着"
- }
复制代码这段代码我建议手打,而且最好用notepad++编辑,不然服务器识别不出来就完蛋,而且缩进最好用tab键。(我是基于metamod包里面自带的那个vdf文件修改的) - 运行服务器查看插件运行状态
没意外的情况下,服务器应该能运行起来了,这个时候在控制台输入plugin_print,应该可以看到插件了(如果只有两条杠,杠之间啥也没有,那你失败了,重新来过),或者你也可以留言寻求帮助(当然你得问明白),这个只是一个什么也没有做的空插件,我会在这个论坛写一些插件供参考。 - 尾声
到这儿成功的朋友和我击个掌吧!(挺不容易的),因为这个时候你已经游于自由的海洋之中了,我可以毫不负责任的讲,现在你啥都可以做,你可以在cpp文件里引用windows.h然后为你的插件创建一个窗口,窗口里调用d3d功能然后模拟一个客户端绘制并且监控当前服务器内正在发生画面(这个可以做到,当然我不会做,因为需要算法的知识),还可以与酷Q机器人进行对接,不需要数据库,实现QQ群内输入指令检查当前服务器的人数和所有玩家的名称,能想到并且能做到的事情太多了,还能够把代码交给单片机集群去处理(相信我,能实现),甚至还能够与MC服务器进行数据对接,非常神奇,然而这一切的一切,不过就是从一个小小的HelloWorld与一份浇不灭的信心,相信想钻的读者一定能够成功,最后,自由万岁
|