// // YaPB - Counter-Strike Bot based on PODBot by Markus Klinge. // Copyright © 2004-2023 YaPB Project . // // SPDX-License-Identifier: MIT // #include ConVar cv_version ("yb_version", product.version.chars (), Var::ReadOnly); gamefuncs_t dllapi; newgamefuncs_t newapi; enginefuncs_t engfuncs; gamedll_funcs_t dllfuncs; meta_globals_t *gpMetaGlobals = nullptr; gamedll_funcs_t *gpGamedllFuncs = nullptr; mutil_funcs_t *gpMetaUtilFuncs = nullptr; globalvars_t *globals = nullptr; // metamod plugin information plugin_info_t Plugin_info = { META_INTERFACE_VERSION, // interface version product.name.chars (), // plugin name product.version.chars (), // plugin version product.date.chars (), // date of creation product.author.chars (), // plugin author product.url.chars (), // plugin URL product.logtag.chars (), // plugin logtag PT_CHANGELEVEL, // when loadable PT_ANYTIME, // when unloadable }; void hook_ClientCommand (edict_t *ent, char const *format, ...) { // this function forces the client whose player entity is ent to issue a client command. // How it works is that clients all have a argv global string in their client DLL that // stores the command string; if ever that string is filled with characters, the client DLL // sends it to the engine as a command to be executed. When the engine has executed that // command, this argv string is reset to zero. Here is somehow a curious implementation of // ClientCommand: the engine sets the command it wants the client to issue in his argv, then // the client DLL sends it back to the engine, the engine receives it then executes the // command therein. Don't ask me why we need all this complicated crap. Anyhow since bots have // no client DLL, be certain never to call this function upon a bot entity, else it will just // make the server crash. Since hordes of uncautious, not to say stupid, programmers don't // even imagine some players on their servers could be bots, this check is performed less than // sometimes actually by their side, that's why we strongly recommend to check it here too. In // case it's a bot asking for a client command, we handle it like we do for bot commands if (game.isNullEntity (ent)) { if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_SUPERCEDE); } return; } va_list ap; auto buffer = strings.chars (); va_start (ap, format); vsnprintf (buffer, StringBuffer::StaticBufferSize, format, ap); va_end (ap); if (util.isFakeClient (ent) || (ent->v.flags & FL_DORMANT)) { auto bot = bots[ent]; if (bot) { bot->issueCommand (buffer); } if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands } return; } if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnClientCommand (ent, buffer); } CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) { // this function is called right after GiveFnptrsToDll() by the engine in the game DLL (or // what it BELIEVES to be the game DLL), in order to copy the list of MOD functions that can // be called by the engine, into a memory block pointed to by the functionTable pointer // that is passed into this function (explanation comes straight from botman). This allows // the Half-Life engine to call these MOD DLL functions when it needs to spawn an entity, // connect or disconnect a player, call Think() functions, Touch() functions, or Use() // functions, etc. The bot DLL passes its OWN list of these functions back to the Half-Life // engine, and then calls the MOD DLL's version of GetEntityAPI to get the REAL gamedll // functions this time (to use in the bot code). plat.bzero (table, sizeof (gamefuncs_t)); if (!(game.is (GameFlags::Metamod))) { auto api_GetEntityAPI = game.lib ().resolve (__FUNCTION__); // pass other DLLs engine callbacks to function table... if (!api_GetEntityAPI || api_GetEntityAPI (&dllapi, INTERFACE_VERSION) == 0) { logger.fatal ("Could not resolve symbol \"%s\" in the game dll.", __FUNCTION__); } dllfuncs.dllapi_table = &dllapi; gpGamedllFuncs = &dllfuncs; memcpy (table, &dllapi, sizeof (gamefuncs_t)); } table->pfnGameInit = [] () { // this function is a one-time call, and appears to be the second function called in the // DLL after GiveFntprsToDll() has been called. Its purpose is to tell the MOD DLL to // initialize the game before the engine actually hooks into it with its video frames and // clients connecting. Note that it is a different step than the *server* initialization. // This one is called once, and only once, when the game process boots up before the first // server is enabled. Here is a good place to do our own game session initialization, and // to register by the engine side the server commands we need to administrate our bots. // execute main config conf.loadMainConfig (true); conf.adjustWeaponPrices (); // print info about dll game.printBotVersion (); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnGameInit (); }; table->pfnSpawn = [] (edict_t *ent) { // this function asks the game DLL to spawn (i.e, give a physical existence in the virtual // world, in other words to 'display') the entity pointed to by ent in the game. The // Spawn() function is one of the functions any entity is supposed to have in the game DLL, // and any MOD is supposed to implement one for each of its entities. auto bot = bots[ent]; if (bot) { bot->spawned (); } // precache everything game.precache (); if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_IGNORED, 0); } int result = dllapi.pfnSpawn (ent); // get result if (ent->v.rendermode == kRenderTransTexture) { ent->v.flags &= ~FL_WORLDBRUSH; // clear the FL_WORLDBRUSH flag out of transparent ents } return result; }; table->pfnTouch = [] (edict_t *pentTouched, edict_t *pentOther) { // this function is called when two entities' bounding boxes enter in collision. For example, // when a player walks upon a gun, the player entity bounding box collides to the gun entity // bounding box, and the result is that this function is called. It is used by the game for // taking the appropriate action when such an event occurs (in our example, the player who // is walking upon the gun will "pick it up"). Entities that "touch" others are usually // entities having a velocity, as it is assumed that static entities (entities that don't // move) will never touch anything. Hence, in our example, the pentTouched will be the gun // (static entity), whereas the pentOther will be the player (as it is the one moving). When // the two entities both have velocities, for example two players colliding, this function // is called twice, once for each entity moving. if (!game.isNullEntity (pentTouched) && pentOther != game.getStartEntity ()) { auto bot = bots[pentTouched]; if (bot && game.isShootableBreakable (pentOther)) { bot->checkBreakable (pentOther); } } if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnTouch (pentTouched, pentOther); }; table->pfnClientConnect = [] (edict_t *ent, const char *name, const char *addr, char rejectReason[128]) { // this function is called in order to tell the MOD DLL that a client attempts to connect the // game. The entity pointer of this client is ent, the name under which he connects is // pointed to by the pszName pointer, and its IP address string is pointed by the pszAddress // one. Note that this does not mean this client will actually join the game ; he could as // well be refused connection by the server later, because of latency timeout, unavailable // game resources, or whatever reason. In which case the reason why the game DLL (read well, // the game DLL, *NOT* the engine) refuses this player to connect will be printed in the // rejectReason string in all letters. Understand that a client connecting process is done // in three steps. First, the client requests a connection from the server. This is engine // internals. When there are already too many players, the engine will refuse this client to // connect, and the game DLL won't even notice. Second, if the engine sees no problem, the // game DLL is asked. This is where we are. Once the game DLL acknowledges the connection, // the client downloads the resources it needs and synchronizes its local engine with the one // of the server. And then, the third step, which comes *AFTER* ClientConnect (), is when the // client officially enters the game, through the ClientPutInServer () function, later below. // Here we hook this function in order to keep track of the listen server client entity, // because a listen server client always connects with a "loopback" address string. Also we // tell the bot manager to check the bot population, in order to always have one free slot on // the server for incoming clients. // check if this client is the listen server client if (strcmp (addr, "loopback") == 0) { game.setLocalEntity (ent); // save the edict of the listen server client... // if not dedicated set the default editor for graph if (!game.isDedicated ()) { graph.setEditor (ent); } } if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_IGNORED, 0); } return dllapi.pfnClientConnect (ent, name, addr, rejectReason); }; table->pfnClientDisconnect = [] (edict_t *ent) { // this function is called whenever a client is VOLUNTARILY disconnected from the server, // either because the client dropped the connection, or because the server dropped him from // the game (latency timeout). The effect is the freeing of a client slot on the server. Note // that clients and bots disconnected because of a level change NOT NECESSARILY call this // function, because in case of a level change, it's a server shutdown, and not a normal // disconnection. I find that completely stupid, but that's it. Anyway it's time to update // the bots and players counts, and in case the client disconnecting is a bot, to back its // brain(s) up to disk. We also try to notice when a listenserver client disconnects, so as // to reset his entity pointer for safety. There are still a few server frames to go once a // listen server client disconnects, and we don't want to send him any sort of message then. for (auto &bot : bots) { if (bot->pev == &ent->v) { bots.erase (bot.get ()); // remove the bot from bots array break; } } // clear the graph editor upon disconnect if (ent == graph.getEditor ()) { graph.setEditor (nullptr); } // clear issuer for the menus and commands if (ent == ctrl.getIssuer ()) { ctrl.setIssuer (nullptr); } if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnClientDisconnect (ent); }; table->pfnClientUserInfoChanged = [] (edict_t *ent, char *infobuffer) { // this function is called when a player changes model, or changes team. Occasionally it // enforces rules on these changes (for example, some MODs don't want to allow players to // change their player model). But most commonly, this function is in charge of handling // team changes, recounting the teams population, etc... ctrl.assignAdminRights (ent, infobuffer); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnClientUserInfoChanged (ent, infobuffer); }; table->pfnClientCommand = [] (edict_t *ent) { // this function is called whenever the client whose player entity is ent issues a client // command. How it works is that clients all have a global string in their client DLL that // stores the command string; if ever that string is filled with characters, the client DLL // sends it to the engine as a command to be executed. When the engine has executed that // command, that string is reset to zero. By the server side, we can access this string // by asking the engine with the CmdArgv(), CmdArgs() and CmdArgc() functions that work just // like executable files argument processing work in C (argc gets the number of arguments, // command included, args returns the whole string, and argv returns the wanted argument // only). Here is a good place to set up either bot debug commands the listen server client // could type in his game console, or real new client commands, but we wouldn't want to do // so as this is just a bot DLL, not a MOD. The purpose is not to add functionality to // clients. Hence it can lack of commenting a bit, since this code is very subject to change. if (ctrl.handleClientCommands (ent)) { if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_SUPERCEDE); } return; } else if (ctrl.handleMenuCommands (ent)) { if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_SUPERCEDE); } return; } // record stuff about radio and chat bots.captureChatRadio (engfuncs.pfnCmd_Argv (0), engfuncs.pfnCmd_Argv (1), ent); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnClientCommand (ent); }; table->pfnServerActivate = [] (edict_t *edictList, int edictCount, int clientMax) { // this function is called when the server has fully loaded and is about to manifest itself // on the network as such. Since a mapchange is actually a server shutdown followed by a // restart, this function is also called when a new map is being loaded. Hence it's the // perfect place for doing initialization stuff for our bots, such as reading the BSP data, // loading the bot profiles, and drawing the world map (ie, filling the navigation hashtable). // Once this function has been called, the server can be considered as "running". if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnServerActivate (edictList, edictCount, clientMax); // do a level initialization game.levelInitialize (edictList, edictCount); }; table->pfnServerDeactivate = [] () { // this function is called when the server is shutting down. A particular note about map // changes: changing the map means shutting down the server and starting a new one. Of course // this process is transparent to the user, but either in single player when the hero reaches // a new level and in multiplayer when it's time for a map change, be aware that what happens // is that the server actually shuts down and restarts with a new map. Hence we can use this // function to free and deinit anything which is map-specific, for example we free the memory // space we m'allocated for our BSP data, since a new map means new BSP data to interpret. In // any case, when the new map will be booting, ServerActivate() will be called, so we'll do // the loading of new bots and the new BSP data parsing there. // save collected experience on shutdown graph.savePractice (); // destroy global killer entity bots.destroyKillerEntity (); // set state to unprecached game.setUnprecached (); // enable lightstyle animations on level change illum.enableAnimation (true); // send message on new map util.setNeedForWelcome (false); // clear local entity game.setLocalEntity (nullptr); // reset graph state graph.reset (); // clear all the bots bots.destroy (); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnServerDeactivate (); // refill export table ents.flush (); }; table->pfnStartFrame = [] () { // this function starts a video frame. It is called once per video frame by the game. If // you run Half-Life at 90 fps, this function will then be called 90 times per second. By // placing a hook on it, we have a good place to do things that should be done continuously // during the game, for example making the bots think (yes, because no Think() function exists // for the bots by the MOD side, remember). Also here we have control on the bot population, // for example if a new player joins the server, we should disconnect a bot, and if the // player population decreases, we should fill the server with other bots. // update lightstyle animations illum.animateLight (); // update some stats for clients util.updateClients (); if (graph.hasEditFlag (GraphEdit::On) && graph.hasEditor ()) { graph.frame (); } // run stuff periodically game.slowFrame (); if (bots.hasBotsOnline ()) { // keep track of grenades on map bots.updateActiveGrenade (); // keep track of intresting entities bots.updateIntrestingEntities (); } // keep bot number up to date bots.maintainQuota (); // flush print queue to users ctrl.flushPrintQueue (); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnStartFrame (); // run the bot ai bots.frame (); }; table->pfnCmdStart = [] (const edict_t *player, usercmd_t *cmd, unsigned int random_seed) { auto ent = const_cast (player); // if we're handle pings for bots and clients, clear IN_SCORE button so SV_ShouldUpdatePing engine function return false, and SV_EmitPings will not overwrite our results if (game.is (GameFlags::HasFakePings) && cv_show_latency.int_ () == 2) { if (!util.isFakeClient (ent) && (ent->v.oldbuttons | ent->v.button | cmd->buttons) & IN_SCORE) { cmd->buttons &= ~IN_SCORE; util.emitPings (ent); } } if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnCmdStart (ent, cmd, random_seed); }; table->pfnPM_Move = [] (playermove_t *pm, int server) { // this is the player movement code clients run to predict things when the server can't update // them often enough (or doesn't want to). The server runs exactly the same function for // moving players. There is normally no distinction between them, else client-side prediction // wouldn't work properly (and it doesn't work that well, already...) illum.setWorldModel (pm->physents[0].model); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnPM_Move (pm, server); }; return HLTrue; } CR_LINKAGE_C int GetEntityAPI_Post (gamefuncs_t *table, int) { // this function is called right after GiveFnptrsToDll() by the engine in the game DLL (or // what it BELIEVES to be the game DLL), in order to copy the list of MOD functions that can // be called by the engine, into a memory block pointed to by the functionTable pointer // that is passed into this function (explanation comes straight from botman). This allows // the Half-Life engine to call these MOD DLL functions when it needs to spawn an entity, // connect or disconnect a player, call Think() functions, Touch() functions, or Use() // functions, etc. The bot DLL passes its OWN list of these functions back to the Half-Life // engine, and then calls the MOD DLL's version of GetEntityAPI to get the REAL gamedll // functions this time (to use in the bot code). Post version, called only by metamod. plat.bzero (table, sizeof (gamefuncs_t)); table->pfnSpawn = [] (edict_t *ent) { // this function asks the game DLL to spawn (i.e, give a physical existence in the virtual // world, in other words to 'display') the entity pointed to by ent in the game. The // Spawn() function is one of the functions any entity is supposed to have in the game DLL, // and any MOD is supposed to implement one for each of its entities. Post version called // only by metamod. // solves the bots unable to see through certain types of glass bug. if (ent->v.rendermode == kRenderTransTexture) { ent->v.flags &= ~FL_WORLDBRUSH; // clear the FL_WORLDBRUSH flag out of transparent ents } RETURN_META_VALUE (MRES_HANDLED, 0); }; table->pfnStartFrame = [] () { // this function starts a video frame. It is called once per video frame by the game. If // you run Half-Life at 90 fps, this function will then be called 90 times per second. By // placing a hook on it, we have a good place to do things that should be done continuously // during the game, for example making the bots think (yes, because no Think() function exists // for the bots by the MOD side, remember). Post version called only by metamod. // run the bot ai bots.frame (); RETURN_META (MRES_IGNORED); }; table->pfnServerActivate = [] (edict_t *edictList, int edictCount, int) { // this function is called when the server has fully loaded and is about to manifest itself // on the network as such. Since a mapchange is actually a server shutdown followed by a // restart, this function is also called when a new map is being loaded. Hence it's the // perfect place for doing initialization stuff for our bots, such as reading the BSP data, // loading the bot profiles, and drawing the world map (ie, filling the navigation hashtable). // Once this function has been called, the server can be considered as "running". Post version // called only by metamod. // do a level initialization game.levelInitialize (edictList, edictCount); RETURN_META (MRES_IGNORED); }; return HLTrue; } CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) { if (game.is (GameFlags::Metamod)) { plat.bzero (table, sizeof (enginefuncs_t)); } if (ents.needsBypass () && !game.is (GameFlags::Metamod)) { table->pfnCreateNamedEntity = [] (int classname) -> edict_t *{ if (ents.isPaused ()) { ents.enable (); ents.setPaused (false); } return engfuncs.pfnCreateNamedEntity (classname); }; } table->pfnLightStyle = [] (int style, char *val) { // ths function update lightstyle for the bots illum.updateLight (style, val); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnLightStyle (style, val); }; if (game.is (GameFlags::Legacy)) { table->pfnFindEntityByString = [] (edict_t *edictStartSearchAfter, const char *field, const char *value) { // round starts in counter-strike 1.5 if (strcmp (value, "info_map_parameters") == 0) { bots.initRound (); } if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_IGNORED, static_cast (nullptr)); } return engfuncs.pfnFindEntityByString (edictStartSearchAfter, field, value); }; } table->pfnEmitSound = [] (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) { // this function tells the engine that the entity pointed to by "entity", is emitting a sound // which fileName is "sample", at level "channel" (CHAN_VOICE, etc...), with "volume" as // loudness multiplicator (normal volume VOL_NORM is 1.0), with a pitch of "pitch" (normal // pitch PITCH_NORM is 100.0), and that this sound has to be attenuated by distance in air // according to the value of "attenuation" (normal attenuation ATTN_NORM is 0.8 ; ATTN_NONE // means no attenuation with distance). Optionally flags "fFlags" can be passed, which I don't // know the heck of the purpose. After we tell the engine to emit the sound, we have to call // SoundAttachToThreat() to bring the sound to the ears of the bots. Since bots have no client DLL // to handle this for them, such a job has to be done manually. util.listenNoise (entity, sample, volume); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnEmitSound (entity, channel, sample, volume, attenuation, flags, pitch); }; table->pfnMessageBegin = [] (int msgDest, int msgType, const float *origin, edict_t *ed) { // this function called each time a message is about to sent. msgs.start (ed, msgType); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnMessageBegin (msgDest, msgType, origin, ed); }; if (!game.is (GameFlags::Metamod)) { table->pfnMessageEnd = [] () { engfuncs.pfnMessageEnd (); // this allows us to send messages right in handler code msgs.stop (); }; } table->pfnWriteByte = [] (int value) { // if this message is for a bot, call the client message function... msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteByte (value); }; table->pfnWriteChar = [] (int value) { // if this message is for a bot, call the client message function... msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteChar (value); }; table->pfnWriteShort = [] (int value) { // if this message is for a bot, call the client message function... msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteShort (value); }; table->pfnWriteLong = [] (int value) { // if this message is for a bot, call the client message function... msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteLong (value); }; table->pfnWriteAngle = [] (float value) { // if this message is for a bot, call the client message function... msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteAngle (value); }; table->pfnWriteCoord = [] (float value) { // if this message is for a bot, call the client message function... msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteCoord (value); }; table->pfnWriteString = [] (const char *sz) { // if this message is for a bot, call the client message function... msgs.collect (sz); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteString (sz); }; table->pfnWriteEntity = [] (int value) { // if this message is for a bot, call the client message function... msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteEntity (value); }; if (!game.is (GameFlags::Metamod)) { table->pfnRegUserMsg = [] (const char *name, int size) { // this function registers a "user message" by the engine side. User messages are network // messages the game DLL asks the engine to send to clients. Since many MODs have completely // different client features (Counter-Strike has a radar and a timer, for example), network // messages just can't be the same for every MOD. Hence here the MOD DLL tells the engine, // "Hey, you have to know that I use a network message whose name is pszName and it is size // packets long". The engine books it, and returns the ID number under which he recorded that // custom message. Thus every time the MOD DLL will be wanting to send a message named pszName // using pfnMessageBegin (), it will know what message ID number to send, and the engine will // know what to do, only for non-metamod version return msgs.add (name, engfuncs.pfnRegUserMsg (name, size)); // return privously registered message }; } table->pfnClientPrintf = [] (edict_t *ent, PRINT_TYPE printType, const char *message) { // this function prints the text message string pointed to by message by the client side of // the client entity pointed to by ent, in a manner depending of printType (print_console, // print_center or print_chat). Be certain never to try to feed a bot with this function, // as it will crash your server. Why would you, anyway ? bots have no client DLL as far as // we know, right ? But since stupidity rules this world, we do a preventive check :) if (util.isFakeClient (ent)) { if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_SUPERCEDE); } return; } if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnClientPrintf (ent, printType, message); }; table->pfnCmd_Args = [] () { // this function returns a pointer to the whole current client command string. Since bots // have no client DLL and we may want a bot to execute a client command, we had to implement // a argv string in the bot DLL for holding the bots' commands, and also keep track of the // argument count. Hence this hook not to let the engine ask an unexistent client DLL for a // command we are holding here. Of course, real clients commands are still retrieved the // normal way, by asking the game. // is this a bot issuing that client command? if (game.isBotCmd ()) { if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_SUPERCEDE, game.botArgs ()); } return game.botArgs (); // else return the whole bot client command string we know } if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_IGNORED, static_cast (nullptr)); } return engfuncs.pfnCmd_Args (); // ask the client command string to the engine }; table->pfnCmd_Argv = [] (int argc) { // this function returns a pointer to a certain argument of the current client command. Since // bots have no client DLL and we may want a bot to execute a client command, we had to // implement a argv string in the bot DLL for holding the bots' commands, and also keep // track of the argument count. Hence this hook not to let the engine ask an unexistent client // DLL for a command we are holding here. Of course, real clients commands are still retrieved // the normal way, by asking the game. // is this a bot issuing that client command? if (game.isBotCmd ()) { if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_SUPERCEDE, game.botArgv (argc)); } return game.botArgv (argc); // if so, then return the wanted argument we know } if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_IGNORED, static_cast (nullptr)); } return engfuncs.pfnCmd_Argv (argc); // ask the argument number "argc" to the engine }; table->pfnCmd_Argc = [] () { // this function returns the number of arguments the current client command string has. Since // bots have no client DLL and we may want a bot to execute a client command, we had to // implement a argv string in the bot DLL for holding the bots' commands, and also keep // track of the argument count. Hence this hook not to let the engine ask an unexistent client // DLL for a command we are holding here. Of course, real clients commands are still retrieved // the normal way, by asking the game. // is this a bot issuing that client command? if (game.isBotCmd ()) { if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_SUPERCEDE, game.botArgc ()); } return game.botArgc (); // if so, then return the argument count we know } if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_IGNORED, 0); } return engfuncs.pfnCmd_Argc (); // ask the engine how many arguments there are }; table->pfnSetClientMaxspeed = [] (const edict_t *ent, float newMaxspeed) { auto bot = bots[const_cast (ent)]; // check wether it's not a bot if (bot != nullptr) { bot->pev->maxspeed = newMaxspeed; } if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnSetClientMaxspeed (ent, newMaxspeed); }; table->pfnClientCommand = hook_ClientCommand; return HLTrue; } CR_EXPORT int GetNewDLLFunctions (newgamefuncs_t *table, int *interfaceVersion) { // it appears that an extra function table has been added in the engine to gamedll interface // since the date where the first enginefuncs table standard was frozen. These ones are // facultative and we don't hook them, but since some MODs might be featuring it, we have to // pass them too, else the DLL interfacing wouldn't be complete and the game possibly wouldn't // run properly. plat.bzero (table, sizeof (newgamefuncs_t)); if (!(game.is (GameFlags::Metamod))) { auto api_GetNewDLLFunctions = game.lib ().resolve (__FUNCTION__); // pass other DLLs engine callbacks to function table... if (!api_GetNewDLLFunctions || api_GetNewDLLFunctions (&newapi, interfaceVersion) == 0) { logger.error ("Could not resolve symbol \"%s\" in the game dll.", __FUNCTION__); } dllfuncs.newapi_table = &newapi; memcpy (table, &newapi, sizeof (newgamefuncs_t)); } if (!game.is (GameFlags::Legacy)) { table->pfnOnFreeEntPrivateData = [] (edict_t *ent) { for (auto &bot : bots) { if (bot->m_enemy == ent) { bot->m_enemy = nullptr; bot->m_lastEnemy = nullptr; } } if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } newapi.pfnOnFreeEntPrivateData (ent); }; } return HLTrue; } CR_LINKAGE_C int GetEngineFunctions_Post (enginefuncs_t *table, int *) { plat.bzero (table, sizeof (enginefuncs_t)); table->pfnMessageEnd = [] () { msgs.stop (); // this allows us to send messages right in handler code RETURN_META (MRES_IGNORED); }; table->pfnRegUserMsg = [] (const char *name, int) { // this function registers a "user message" by the engine side. User messages are network // messages the game DLL asks the engine to send to clients. Since many MODs have completely // different client features (Counter-Strike has a radar and a timer, for example), network // messages just can't be the same for every MOD. Hence here the MOD DLL tells the engine, // "Hey, you have to know that I use a network message whose name is pszName and it is size // packets long". The engine books it, and returns the ID number under which he recorded that // custom message. Thus every time the MOD DLL will be wanting to send a message named pszName // using pfnMessageBegin (), it will know what message ID number to send, and the engine will // know what to do, only for non-metamod version // register message for our needs msgs.add (name, META_RESULT_ORIG_RET (int)); RETURN_META_VALUE (MRES_IGNORED, 0); }; return HLTrue; } CR_EXPORT int Meta_Query (char *, plugin_info_t **pPlugInfo, mutil_funcs_t *pMetaUtilFuncs) { // this function is the first function ever called by metamod in the plugin DLL. Its purpose // is for metamod to retrieve basic information about the plugin, such as its meta-interface // version, for ensuring compatibility with the current version of the running metamod. gpMetaUtilFuncs = pMetaUtilFuncs; *pPlugInfo = &Plugin_info; return HLTrue; // tell metamod this plugin looks safe } CR_EXPORT int Meta_Attach (PLUG_LOADTIME now, metamod_funcs_t *functionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs) { // this function is called when metamod attempts to load the plugin. Since it's the place // where we can tell if the plugin will be allowed to run or not, we wait until here to make // our initialization stuff, like registering CVARs and dedicated server commands. // metamod engine & dllapi function tables static metamod_funcs_t metamodFunctionTable = { GetEntityAPI, // pfnGetEntityAPI () GetEntityAPI_Post, // pfnGetEntityAPI_Post () nullptr, // pfnGetEntityAPI2 () nullptr, // pfnGetEntityAPI2_Post () GetNewDLLFunctions, // pfnGetNewDLLFunctions () nullptr, // pfnGetNewDLLFunctions_Post () GetEngineFunctions, // pfnGetEngineFunctions () GetEngineFunctions_Post, // pfnGetEngineFunctions_Post () }; if (now > Plugin_info.loadable) { logger.error ("%s: plugin NOT attaching (can't load plugin right now)", Plugin_info.name); return HLFalse; // returning FALSE prevents metamod from attaching this plugin } // keep track of the pointers to engine function tables metamod gives us gpMetaGlobals = pMGlobals; memcpy (functionTable, &metamodFunctionTable, sizeof (metamod_funcs_t)); gpGamedllFuncs = pGamedllFuncs; return HLTrue; // returning true enables metamod to attach this plugin } CR_EXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) { // this function is called when metamod unloads the plugin. A basic check is made in order // to prevent unloading the plugin if its processing should not be interrupted. if (now > Plugin_info.unloadable && reason != PNL_CMD_FORCED) { logger.error ("%s: plugin NOT detaching (can't unload plugin right now)", Plugin_info.name); return HLFalse; // returning FALSE prevents metamod from unloading this plugin } bots.kickEveryone (true); // kick all bots off this server // save collected experience on shutdown graph.savePractice (); util.disableSendTo (); // make sure all stuff cleared bots.destroy (); return HLTrue; } CR_EXPORT void Meta_Init () { // this function is called by metamod, before any other interface functions. Purpose of this // function to give plugin a chance to determine is plugin running under metamod or not. game.addGameFlag (GameFlags::Metamod); } // games GiveFnptrsToDll is a bit tricky #if defined(CR_WINDOWS) # if defined(CR_CXX_MSVC) || (defined(CR_CXX_CLANG) && !defined(CR_CXX_GCC)) # if defined (CR_ARCH_X86) # pragma comment(linker, "/EXPORT:GiveFnptrsToDll=_GiveFnptrsToDll@8,@1") # endif # pragma comment(linker, "/SECTION:.data,RW") # endif # if defined(CR_CXX_MSVC) && !defined(CR_ARCH_X64) # define DLL_GIVEFNPTRSTODLL CR_LINKAGE_C void CR_STDCALL # elif defined(CR_CXX_CLANG) || defined(CR_CXX_GCC) || defined(CR_ARCH_X64) # define DLL_GIVEFNPTRSTODLL CR_EXPORT void CR_STDCALL # endif #else # define DLL_GIVEFNPTRSTODLL CR_EXPORT void #endif DLL_GIVEFNPTRSTODLL GiveFnptrsToDll (enginefuncs_t *table, globalvars_t *glob) { // this is the very first function that is called in the game DLL by the game. Its purpose // is to set the functions interfacing up, by exchanging the functionTable function list // along with a pointer to the engine's global variables structure pGlobals, with the game // DLL. We can there decide if we want to load the normal game DLL just behind our bot DLL, // or any other game DLL that is present, such as Will Day's metamod. Also, since there is // a known bug on Win32 platforms that prevent hook DLLs (such as our bot DLL) to be used in // single player games (because they don't export all the stuff they should), we may need to // build our own array of exported symbols from the actual game DLL in order to use it as // such if necessary. Nothing really bot-related is done in this function. The actual bot // initialization stuff will be done later, when we'll be certain to have a multilayer game. // get the engine functions from the game... memcpy (&engfuncs, table, sizeof (enginefuncs_t)); globals = glob; if (game.postload ()) { return; } auto api_GiveFnptrsToDll = game.lib ().resolve (__FUNCTION__); if (!api_GiveFnptrsToDll) { logger.fatal ("Could not resolve symbol \"%s\" in the game dll.", __FUNCTION__); } GetEngineFunctions (table, nullptr); // initialize dynamic linkents (no memory hacking with xash3d) if (!game.is (GameFlags::Xash3D)) { ents.initialize (); } // give the engine functions to the other DLL... api_GiveFnptrsToDll (table, glob); } CR_EXPORT int Server_GetBlendingInterface (int version, struct sv_blending_interface_s **ppinterface, struct engine_studio_api_s *pstudio, float *rotationmatrix, float *bonetransform) { // this function synchronizes the studio model animation blending interface (i.e, what parts // of the body move, which bones, which hitboxes and how) between the server and the game DLL. // some MODs can be using a different hitbox scheme than the standard one. auto api_GetBlendingInterface = game.lib ().resolve (__FUNCTION__); if (!api_GetBlendingInterface) { logger.error ("Could not resolve symbol \"%s\" in the game dll. Continuing...", __FUNCTION__); return HLFalse; } return api_GetBlendingInterface (version, ppinterface, pstudio, rotationmatrix, bonetransform); } CR_EXPORT int Server_GetPhysicsInterface (int version, server_physics_api_t *physics_api, physics_interface_t *table) { // this function handle the custom xash3d physics interface, that we're uses just for resolving // entities between game and engine. if (!table || !physics_api || version != SV_PHYSICS_INTERFACE_VERSION) { return HLFalse; } table->version = SV_PHYSICS_INTERFACE_VERSION; table->SV_CreateEntity = [] (edict_t *ent, const char *name) -> int { auto func = game.lib ().resolve (name); // lookup symbol in game dll // found one in game dll ? if (func) { func (&ent->v); return HLTrue; } return -1; }; table->SV_PhysicsEntity = [] (edict_t *) -> int { return HLFalse; }; return HLTrue; } SharedLibrary::Func EntityLinkage::lookup (SharedLibrary::Handle module, const char *function) { static const auto &gamedll = game.lib ().handle (); static const auto &self = m_self.handle (); const auto resolve = [&] (SharedLibrary::Handle handle) { return m_dlsym (handle, function); }; if (ents.needsBypass () && !strcmp (function, "CreateInterface")) { ents.setPaused (true); auto ret = resolve (module); ents.disable (); return ret; } // if requested module is yapb module, put in cache the looked up symbol if (self != module) { return resolve (module); } #if defined (CR_WINDOWS) if (HIWORD (function) == 0) { return resolve (module); } #endif if (m_exports.has (function)) { return m_exports[function]; } auto botAddr = resolve (self); if (!botAddr) { auto gameAddr = resolve (gamedll); if (gameAddr) { return m_exports[function] = gameAddr; } } else { return m_exports[function] = botAddr; } return nullptr; } void EntityLinkage::callPlayerFunction (edict_t *ent) { EntityFunction playerFunction = nullptr; if (game.is (GameFlags::Xash3D)) { playerFunction = game.lib ().resolve ("player"); } else { playerFunction = reinterpret_cast (reinterpret_cast (lookup (game.lib ().handle (), "player"))); } if (!playerFunction) { logger.fatal ("Cannot resolve player () function in gamedll."); } else { playerFunction (&ent->v); } } void EntityLinkage::initialize () { if (plat.arm || game.is (GameFlags::Metamod)) { return; } m_dlsym.initialize ("kernel32.dll", "GetProcAddress", DLSYM_FUNCTION); m_dlsym.install (reinterpret_cast (EntityLinkage::lookupHandler), true); if (needsBypass ()) { m_dlclose.initialize ("kernel32.dll", "FreeLibrary", DLCLOSE_FUNCTION); m_dlclose.install (reinterpret_cast (EntityLinkage::closeHandler), true); } m_self.locate (&engfuncs); } // add linkents for android #include "entities.cpp" // override new/delete globally, need to be included in .cpp file #include