技术宅的结界

 找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 87|回复: 0
收起左侧

【Source SDK using C++】使用Source SDK编写Left4Dead2服务器的插件

[复制链接]

1

主题

8

帖子

65

积分

用户组: 小·技术宅

UID
3269
精华
0
威望
1 点
宅币
55 个
贡献
0 次
宅之契约
0 份
在线时间
9 小时
注册时间
2017-12-28
发表于 2018-6-26 11:56:22 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有帐号?立即注册→加入我们

x
本帖最后由 吔 ! 于 2018-6-26 11:59 编辑

【写在前面】


写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.liblibc;libcd;libcmt,至此,项目设置应该是完成了,其他东西也没必要动了。

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

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

    1. #define GAME_DLL
    2. #include <stdio.h>
    3. #include "interface.h"
    4. #include "filesystem.h"
    5. #include "engine/iserverplugin.h"
    6. #include "game/server/iplayerinfo.h"
    7. #include "eiface.h"
    8. #include "igameevents.h"
    9. #include "convar.h"
    10. #include "Color.h"
    11. #include "vstdlib/random.h"
    12. #include "engine/IEngineTrace.h"
    13. #include "tier2/tier2.h"
    14. #include "shareddefs.h"
    15. #include "tier0/memdbgon.h"

    16. class CEmptyServerPlugin: public IServerPluginCallbacks
    17. {
    18. public:
    19.         CEmptyServerPlugin();
    20.         ~CEmptyServerPlugin();

    21.         // IServerPluginCallbacks methods
    22.         virtual bool                        Load(        CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory );
    23.         virtual void                        Unload( void );
    24.         virtual void                        Pause( void );
    25.         virtual void                        UnPause( void );
    26.         virtual const char     *GetPluginDescription( void );      
    27.         virtual void                        LevelInit( char const *pMapName );
    28.         virtual void                        ServerActivate( edict_t *pEdictList, int edictCount, int clientMax );
    29.         virtual void                        GameFrame( bool simulating );
    30.         virtual void                        LevelShutdown( void );
    31.         virtual void                        ClientActive( edict_t *pEntity );
    32.         virtual void                        ClientDisconnect( edict_t *pEntity );
    33.         virtual void                        ClientPutInServer( edict_t *pEntity, char const *playername );
    34.         virtual void                        SetCommandClient( int index );
    35.         virtual void                        ClientSettingsChanged( edict_t *pEdict );
    36.         virtual PLUGIN_RESULT        ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen );
    37.         virtual PLUGIN_RESULT        ClientCommand( edict_t *pEntity, const CCommand &args );
    38.         virtual PLUGIN_RESULT        NetworkIDValidated( const char *pszUserName, const char *pszNetworkID );
    39.         virtual void                        OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue );

    40. };


    41. //
    42. // The plugin is a static singleton that is exported as an interface
    43. //
    44. CEmptyServerPlugin g_EmtpyServerPlugin;
    45. EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CEmptyServerPlugin, IServerPluginCallbacks, INTERFACEVERSION_ISERVERPLUGINCALLBACKS, g_EmtpyServerPlugin );

    46. //---------------------------------------------------------------------------------
    47. // Purpose: constructor/destructor
    48. //---------------------------------------------------------------------------------
    49. CEmptyServerPlugin::CEmptyServerPlugin()
    50. {
    51. }

    52. CEmptyServerPlugin::~CEmptyServerPlugin()
    53. {
    54. }

    55. //---------------------------------------------------------------------------------
    56. // Purpose: called when the plugin is loaded, load the interface we need from the engine
    57. //---------------------------------------------------------------------------------
    58. bool CEmptyServerPlugin::Load(        CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory )
    59. {
    60.         return true;
    61. }

    62. //---------------------------------------------------------------------------------
    63. // Purpose: called when the plugin is unloaded (turned off)
    64. //---------------------------------------------------------------------------------
    65. void CEmptyServerPlugin::Unload( void )
    66. {
    67. }

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

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

    80. //---------------------------------------------------------------------------------
    81. // Purpose: the name of this plugin, returned in "plugin_print" command
    82. //---------------------------------------------------------------------------------
    83. const char *CEmptyServerPlugin::GetPluginDescription( void )
    84. {
    85.         return "Plugin Made by Yourself";
    86. }

    87. //---------------------------------------------------------------------------------
    88. // Purpose: called on level start
    89. //---------------------------------------------------------------------------------
    90. void CEmptyServerPlugin::LevelInit( char const *pMapName )
    91. {

    92. }

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

    99. }

    100. //---------------------------------------------------------------------------------
    101. // Purpose: called once per server frame, do recurring work here (like checking for timeouts)
    102. //---------------------------------------------------------------------------------
    103. void CEmptyServerPlugin::GameFrame( bool simulating )
    104. {
    105. }

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

    111. }

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

    117. }

    118. //---------------------------------------------------------------------------------
    119. // Purpose: called when a client leaves a server (or is timed out)
    120. //---------------------------------------------------------------------------------
    121. void CEmptyServerPlugin::ClientDisconnect( edict_t *pEntity )
    122. {
    123. }

    124. void CEmptyServerPlugin::OnQueryCvarValueFinished( QueryCvarCookie_t iCookie,
    125.                                                           edict_t *pPlayerEntity,
    126.                                                           EQueryCvarValueStatus eStatus,
    127.                                                           const char *pCvarName,
    128.                                                           const char *pCvarValue )
    129. {
    130. }

    131. void CEmptyServerPlugin::ClientPutInServer(edict_t* pEntity, const char* playername)
    132. {
    133. }

    134. void CEmptyServerPlugin::SetCommandClient(int index)
    135. {

    136. }

    137. void CEmptyServerPlugin::ClientSettingsChanged(edict_t* pEdict)
    138. {

    139. }

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

    144. PLUGIN_RESULT CEmptyServerPlugin::ClientCommand(edict_t* pEntity, const CCommand& args)
    145. {
    146.         return PLUGIN_CONTINUE;
    147. }

    148. PLUGIN_RESULT CEmptyServerPlugin::NetworkIDValidated(const char* pszUserName, const char* pszNetworkID)
    149. {
    150.         return PLUGIN_CONTINUE;
    151. }
    复制代码

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

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

  • 【广告推销时间】我有一个写MC插件的教程,我详细介绍了插件的涵义与含义,以及插件是怎么插进服务器的,与MC服务器相同的是,L4D2服务器也有一个插件引导文件,后缀名为vdf,可以新建一个vdf文件(非中文名),然后放到addons文件夹(这个读者应该清楚吧),在vdf里面写上以下内容:
    1. "Plugin"
    2. {
    3.     "file"    "这里填写你的插件的目录,如果你dll文件放在addons目录里面,那么这里你可以写addons/文件名(不带.dll),注意,最外边两个引号你得留着"
    4. }
    复制代码

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

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

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

  • 尾声

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


本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号|网站地图

GMT+8, 2018-7-18 08:35 , Processed in 0.082803 second(s), 15 queries , Gzip On, Memcache On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表