If some of you want to understand EXACTLY (hopefully, with enough comments) how the plugin interface works, here's a minimalistic bot plugin that fits in 1 file
I'll from now use that as a template to port some of my plugins (like PMTools) to Source
Code:
///////////////////////////////////////////////////////////////
// Minimalistic Source plugin suitable for bots and most addons
// Wrapped together by Pierre-Marie Baty from Bots-United
// http://forums.bots-united.com
#include <stdio.h>
#define PLUGIN_DESCRIPTION "Bot plugin Source template"
#define WrapAngle(a) ((a) == 180 ? -180 : ((a) >= 180 ? (a) - 360 * abs (((int) (a) + 180) / 360) : ((a) < -180 ? (a) + 360 * abs (((int) (a) - 180) / 360) : (a))))
///////////////////////////////////////////////////////////////////////////////////////
// START of plugin interface
#include "interface.h"
#include "filesystem.h"
#undef VECTOR_NO_SLOW_OPERATIONS
#include "vector.h"
#include "edict.h"
#include "engine/iserverplugin.h"
#include "dlls/iplayerinfo.h"
#include "eiface.h"
#include "igameevents.h"
#include "convar.h"
#include "icvar.h"
#include "engine/IEngineTrace.h"
#include "../../game_shared/in_buttons.h"
#include "../../game_shared/shareddefs.h"
#include "tier0/memdbgon.h"// memdbgon must be the last include file in a .cpp file!!!
// plugin interface virtual function table pointers
IVEngineServer *engine = NULL; // helper engine functions
IFileSystem *filesystem = NULL; // file I/O
IGameEventManager *gameeventmanager = NULL; // game events interface
IPlayerInfoManager *playerinfomanager = NULL; // game dll interface to interact with players
IBotManager *botmanager = NULL; // game dll interface to interact with bots
IServerPluginHelpers *helpers = NULL; // special 3rd party plugin helpers from the engine
IEngineTrace *enginetrace = NULL; // engine trace facilities (lines and hulls)
ICvar *serverconsole = NULL; // plugin's own console access
CGlobalVars *gpGlobals = NULL; // server globals
// plugin interface classes
class CConCommandAccessor: public IConCommandBaseAccessor
{
public:
virtual bool RegisterConCommandBase (ConCommandBase *pCommand)
{
pCommand->AddFlags (FCVAR_PLUGIN);
pCommand->SetNext (0); // Unlink from plugin only list
serverconsole->RegisterConCommandBase (pCommand); // Link to engine's list instead
return (true);
}
};
CConCommandAccessor g_ConVarAccessor; // server console commands AND cvars accessor
class CPlugin: public IServerPluginCallbacks, public IGameEventListener
{
public:
// IServerPluginCallbacks interface
virtual bool Load (CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory)
{
// get the interfaces we want to use
if (!(playerinfomanager = (IPlayerInfoManager *) gameServerFactory (INTERFACEVERSION_PLAYERINFOMANAGER,NULL))
|| !(botmanager = (IBotManager *) gameServerFactory (INTERFACEVERSION_PLAYERBOTMANAGER, NULL))
|| !(engine = (IVEngineServer *) interfaceFactory (INTERFACEVERSION_VENGINESERVER, NULL))
|| !(gameeventmanager = (IGameEventManager *) interfaceFactory (INTERFACEVERSION_GAMEEVENTSMANAGER,NULL))
|| !(filesystem = (IFileSystem *) interfaceFactory (FILESYSTEM_INTERFACE_VERSION, NULL)) // FIXME: not needed for bots perhaps ?
|| !(helpers = (IServerPluginHelpers *) interfaceFactory (INTERFACEVERSION_ISERVERPLUGINHELPERS, NULL))
|| !(enginetrace = (IEngineTrace *) interfaceFactory (INTERFACEVERSION_ENGINETRACE_SERVER,NULL))
|| !(serverconsole = (ICvar *) interfaceFactory (VENGINE_CVAR_INTERFACE_VERSION, NULL)))
return (false); // we require all these interface to function
gpGlobals = playerinfomanager->GetGlobalVars (); // get a pointer to the engine's globalvars
ConCommandBaseMgr::OneTimeInit (&g_ConVarAccessor); // register any cvars and server commands
return (true); // return true so as to enable the engine to load this plugin
}
virtual void Unload (void) { gameeventmanager->RemoveListener (this); }
virtual void Pause (void) { }
virtual void UnPause (void) { }
virtual const char *GetPluginDescription (void) { return (PLUGIN_DESCRIPTION); }
virtual void LevelInit (char const *pMapName) { gameeventmanager->AddListener (this, true); }
virtual void ServerActivate (edict_t *pEdictList, int edictCount, int clientMax) { }
virtual void GameFrame (bool simulating); // <-- define this one
virtual void LevelShutdown (void) { gameeventmanager->RemoveListener (this); }
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) { return (PLUGIN_CONTINUE); }
virtual PLUGIN_RESULT ClientCommand (edict_t *pEntity) { return (PLUGIN_CONTINUE); }
virtual PLUGIN_RESULT NetworkIDValidated (const char *pszUserName, const char *pszNetworkID) { return (PLUGIN_CONTINUE); }
// IGameEventListener interface
virtual void FireGameEvent (KeyValues * event); // <-- define this one
};
// tell the engine to create a plugin interface instance for us
EXPOSE_SINGLE_INTERFACE (CPlugin, IServerPluginCallbacks, INTERFACEVERSION_ISERVERPLUGINCALLBACKS);
// END of plugin interface
///////////////////////////////////////////////////////////////////////////////////////
typedef struct
{
edict_t *pEdict;
bool is_started;
float f_crouch_time;
float f_jump_time;
float f_forward_time;
float f_backwards_time;
float f_strafeleft_time;
float f_straferight_time;
Vector v_angles;
IBotController *m_BotInterface;
IPlayerInfo *m_PlayerInfo;
CBotCmd cmd;
} bot_t;
// global variables
bot_t bots[64];
int bot_count;
// prototypes
void Bot_RunAll (void);
void BotThink (bot_t *pBot);
void BotSelectTeamAndClass (bot_t *pBot);
// plugin server command handler
CON_COMMAND (bot, "bot server command handler")
{
char cmd[128];
char arg1[128];
char arg2[128];
char arg3[128];
if (botmanager == NULL)
return; // reliability check
sprintf (cmd, engine->Cmd_Argv (0));
sprintf (arg1, engine->Cmd_Argv (1));
sprintf (arg2, engine->Cmd_Argv (2));
sprintf (arg3, engine->Cmd_Argv (3));
// have we been requested to add a bot ?
if (strcmp (arg1, "add") == 0)
{
bots[bot_count].pEdict = botmanager->CreateBot ("w00t w00t");
if (bots[bot_count].pEdict != NULL)
{
bots[bot_count].m_BotInterface = botmanager->GetBotController (bots[bot_count].pEdict);
bots[bot_count].m_PlayerInfo = playerinfomanager->GetPlayerInfo (bots[bot_count].pEdict);
bots[bot_count].is_started = false; // need to select team and class
}
bot_count++;
}
// else have we been asked to kick a bot ?
else if (strcmp (arg1, "kick") == 0)
{
; // bleh
}
// etc...
}
void CPlugin::GameFrame (bool simulating)
{
if (simulating)
Bot_RunAll ();
}
void CPlugin::FireGameEvent (KeyValues *event)
{
// do what you want here to catch any game event (check the keyvalue data)
//Msg ("FireGameEvent: Got event \"%s\"\n", event->GetName ());
}
void Bot_RunAll (void)
{
if (!botmanager)
return;
for (int i = 0; i < bot_count; i++)
BotThink (&bots[i]);
}
void BotSelectTeamAndClass (bot_t *pBot)
{
// this is of course game-specific
helpers->ClientCommand (pBot->pEdict, "joingame");
helpers->ClientCommand (pBot->pEdict, "jointeam 3");
helpers->ClientCommand (pBot->pEdict, "joinclass 0");
}
void BotThink (bot_t *pBot)
{
// Run this Bot's AI for one frame.
memset (&pBot->cmd, 0, sizeof (pBot->cmd));
// is the bot not started yet ?
if (!pBot->is_started)
BotSelectTeamAndClass (pBot); // if so, select team and class
// is the bot dead ?
if (pBot->m_PlayerInfo->IsDead ())
return; // don't do anything, wait for new round (note: on other mods, press attack)
// bot AI here
// finally, ask the engine to perform the bot client movement
pBot->m_BotInterface->RunPlayerMove (&pBot->cmd);
}