// // Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd"). // Copyright (c) YaPB Development Team. // // This software is licensed under the BSD-style license. // Additional exceptions apply. For full license details, see LICENSE.txt or visit: // http://yapb.jeefo.net/license // #include ConVar yb_listenserver_welcome ("yb_listenserver_welcome", "1", VT_NOSERVER); ConVar mp_roundtime ("mp_roundtime", NULL, VT_NOREGISTER); ConVar mp_freezetime ("mp_freezetime", NULL, VT_NOREGISTER); void TraceLine (const Vector &start, const Vector &end, bool ignoreMonsters, bool ignoreGlass, edict_t *ignoreEntity, TraceResult *ptr) { // this function traces a line dot by dot, starting from vecStart in the direction of vecEnd, // ignoring or not monsters (depending on the value of IGNORE_MONSTERS, true or false), and stops // at the first obstacle encountered, returning the results of the trace in the TraceResult structure // ptr. Such results are (amongst others) the distance traced, the hit surface, the hit plane // vector normal, etc. See the TraceResult structure for details. This function allows to specify // whether the trace starts "inside" an entity's polygonal model, and if so, to specify that entity // in ignoreEntity in order to ignore it as a possible obstacle. // this is an overloaded prototype to add IGNORE_GLASS in the same way as IGNORE_MONSTERS work. (*g_engfuncs.pfnTraceLine) (start, end, (ignoreMonsters ? TRUE : FALSE) | (ignoreGlass ? 0x100 : 0), ignoreEntity, ptr); } void TraceLine (const Vector &start, const Vector &end, bool ignoreMonsters, edict_t *ignoreEntity, TraceResult *ptr) { // this function traces a line dot by dot, starting from vecStart in the direction of vecEnd, // ignoring or not monsters (depending on the value of IGNORE_MONSTERS, true or false), and stops // at the first obstacle encountered, returning the results of the trace in the TraceResult structure // ptr. Such results are (amongst others) the distance traced, the hit surface, the hit plane // vector normal, etc. See the TraceResult structure for details. This function allows to specify // whether the trace starts "inside" an entity's polygonal model, and if so, to specify that entity // in ignoreEntity in order to ignore it as a possible obstacle. (*g_engfuncs.pfnTraceLine) (start, end, ignoreMonsters ? TRUE : FALSE, ignoreEntity, ptr); } void TraceHull (const Vector &start, const Vector &end, bool ignoreMonsters, int hullNumber, edict_t *ignoreEntity, TraceResult *ptr) { // this function traces a hull dot by dot, starting from vecStart in the direction of vecEnd, // ignoring or not monsters (depending on the value of IGNORE_MONSTERS, true or // false), and stops at the first obstacle encountered, returning the results // of the trace in the TraceResult structure ptr, just like TraceLine. Hulls that can be traced // (by parameter hull_type) are point_hull (a line), head_hull (size of a crouching player), // human_hull (a normal body size) and large_hull (for monsters?). Not all the hulls in the // game can be traced here, this function is just useful to give a relative idea of spatial // reachability (i.e. can a hostage pass through that tiny hole ?) Also like TraceLine, this // function allows to specify whether the trace starts "inside" an entity's polygonal model, // and if so, to specify that entity in ignoreEntity in order to ignore it as an obstacle. (*g_engfuncs.pfnTraceHull) (start, end, ignoreMonsters ? TRUE : FALSE, hullNumber, ignoreEntity, ptr); } uint16 FixedUnsigned16 (float value, float scale) { int output = (static_cast (value * scale)); if (output < 0) output = 0; if (output > 0xffff) output = 0xffff; return static_cast (output); } short FixedSigned16 (float value, float scale) { int output = (static_cast (value * scale)); if (output > 32767) output = 32767; if (output < -32768) output = -32768; return static_cast (output); } const char *FormatBuffer (const char *format, ...) { va_list ap; static char staticBuffer[3072]; va_start (ap, format); vsprintf (staticBuffer, format, ap); va_end (ap); return &staticBuffer[0]; } bool IsAlive (edict_t *ent) { if (IsEntityNull (ent)) return false; return ent->v.deadflag == DEAD_NO && ent->v.health > 0 && ent->v.movetype != MOVETYPE_NOCLIP; } float GetShootingConeDeviation (edict_t *ent, Vector *position) { const Vector &dir = (*position - (ent->v.origin + ent->v.view_ofs)).Normalize (); MakeVectors (ent->v.v_angle); // he's facing it, he meant it return g_pGlobals->v_forward | dir; } bool IsInViewCone (const Vector &origin, edict_t *ent) { MakeVectors (ent->v.v_angle); if (((origin - (ent->v.origin + ent->v.view_ofs)).Normalize () | g_pGlobals->v_forward) >= cosf (((ent->v.fov > 0 ? ent->v.fov : 90.0f) * 0.5f) * MATH_D2R)) return true; return false; } bool IsVisible (const Vector &origin, edict_t *ent) { if (IsEntityNull (ent)) return false; TraceResult tr; TraceLine (ent->v.origin + ent->v.view_ofs, origin, true, true, ent, &tr); if (tr.flFraction != 1.0) return false; // line of sight is not established return true; // line of sight is valid. } Vector GetEntityOrigin (edict_t *ent) { // this expanded function returns the vector origin of a bounded entity, assuming that any // entity that has a bounding box has its center at the center of the bounding box itself. if (IsEntityNull (ent)) return nullvec; if (ent->v.origin == nullvec) return ent->v.absmin + ent->v.size * 0.5; return ent->v.origin; } void DisplayMenuToClient (edict_t *ent, MenuText *menu) { if (!IsValidPlayer (ent)) return; int clientIndex = IndexOfEntity (ent) - 1; if (menu != NULL) { String tempText = String (menu->menuText); tempText.Replace ("\v", "\n"); char *text = g_localizer->TranslateInput (tempText.GetBuffer ()); tempText = String (text); // make menu looks best for (int i = 0; i <= 9; i++) tempText.Replace (FormatBuffer ("%d.", i), FormatBuffer ("\\r%d.\\w", i)); text = (char *) tempText.GetBuffer (); while (strlen (text) >= 64) { MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, g_netMsg->GetId (NETMSG_SHOWMENU), NULL, ent); WRITE_SHORT (menu->validSlots); WRITE_CHAR (-1); WRITE_BYTE (1); for (int i = 0; i <= 63; i++) WRITE_CHAR (text[i]); MESSAGE_END (); text += 64; } MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, g_netMsg->GetId (NETMSG_SHOWMENU), NULL, ent); WRITE_SHORT (menu->validSlots); WRITE_CHAR (-1); WRITE_BYTE (0); WRITE_STRING (text); MESSAGE_END(); g_clients[clientIndex].menu = menu; } else { MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, g_netMsg->GetId (NETMSG_SHOWMENU), NULL, ent); WRITE_SHORT (0); WRITE_CHAR (0); WRITE_BYTE (0); WRITE_STRING (""); MESSAGE_END(); g_clients[clientIndex].menu = NULL; } CLIENT_COMMAND (ent, "speak \"player/geiger1\"\n"); // Stops others from hearing menu sounds.. } void DecalTrace (entvars_t *pev, TraceResult *trace, int logotypeIndex) { // this function draw spraypaint depending on the tracing results. static Array logotypes; if (logotypes.IsEmpty ()) logotypes = String ("{biohaz;{graf003;{graf004;{graf005;{lambda06;{target;{hand1;{spit2;{bloodhand6;{foot_l;{foot_r").Split (";"); int entityIndex = -1, message = TE_DECAL; int decalIndex = (*g_engfuncs.pfnDecalIndex) (logotypes[logotypeIndex].GetBuffer ()); if (decalIndex < 0) decalIndex = (*g_engfuncs.pfnDecalIndex) ("{lambda06"); if (trace->flFraction == 1.0) return; if (!IsEntityNull (trace->pHit)) { if (trace->pHit->v.solid == SOLID_BSP || trace->pHit->v.movetype == MOVETYPE_PUSHSTEP) entityIndex = IndexOfEntity (trace->pHit); else return; } else entityIndex = 0; if (entityIndex != 0) { if (decalIndex > 255) { message = TE_DECALHIGH; decalIndex -= 256; } } else { message = TE_WORLDDECAL; if (decalIndex > 255) { message = TE_WORLDDECALHIGH; decalIndex -= 256; } } if (logotypes[logotypeIndex].Contains ("{")) { MESSAGE_BEGIN (MSG_BROADCAST, SVC_TEMPENTITY); WRITE_BYTE (TE_PLAYERDECAL); WRITE_BYTE (IndexOfEntity (ENT (pev))); WRITE_COORD (trace->vecEndPos.x); WRITE_COORD (trace->vecEndPos.y); WRITE_COORD (trace->vecEndPos.z); WRITE_SHORT (static_cast (IndexOfEntity (trace->pHit))); WRITE_BYTE (decalIndex); MESSAGE_END (); } else { MESSAGE_BEGIN (MSG_BROADCAST, SVC_TEMPENTITY); WRITE_BYTE (message); WRITE_COORD (trace->vecEndPos.x); WRITE_COORD (trace->vecEndPos.y); WRITE_COORD (trace->vecEndPos.z); WRITE_BYTE (decalIndex); if (entityIndex) WRITE_SHORT (entityIndex); MESSAGE_END(); } } void FreeLibraryMemory (void) { // this function free's all allocated memory g_waypoint->Init (); // frees waypoint data FOR_EACH_AE (g_localizer->m_langTab, it) { delete[] g_localizer->m_langTab[it].original; delete[] g_localizer->m_langTab[it].translated; } g_localizer->m_langTab.RemoveAll (); delete [] g_experienceData; g_experienceData = NULL; } void FakeClientCommand (edict_t *fakeClient, const char *format, ...) { // the purpose of this function is to provide fakeclients (bots) with the same client // command-scripting advantages (putting multiple commands in one line between semicolons) // as real players. It is an improved version of botman's FakeClientCommand, in which you // supply directly the whole string as if you were typing it in the bot's "console". It // is supposed to work exactly like the pfnClientCommand (server-sided client command). va_list ap; static char command[256]; int stop, i, stringIndex = 0; if (IsEntityNull (fakeClient)) return; // reliability check // concatenate all the arguments in one string va_start (ap, format); _vsnprintf (command, sizeof (command), format, ap); va_end (ap); if (IsNullString (command)) return; // if nothing in the command buffer, return g_isFakeCommand = true; // set the "fakeclient command" flag int length = strlen (command); // get the total length of the command string // process all individual commands (separated by a semicolon) one each a time while (stringIndex < length) { int start = stringIndex; // save field start position (first character) while (stringIndex < length && command[stringIndex] != ';') stringIndex++; // reach end of field if (command[stringIndex - 1] == '\n') stop = stringIndex - 2; // discard any trailing '\n' if needed else stop = stringIndex - 1; // save field stop position (last character before semicolon or end) for (i = start; i <= stop; i++) g_fakeArgv[i - start] = command[i]; // store the field value in the g_fakeArgv global string g_fakeArgv[i - start] = 0; // terminate the string stringIndex++; // move the overall string index one step further to bypass the semicolon int index = 0; g_fakeArgc = 0; // let's now parse that command and count the different arguments // count the number of arguments while (index < i - start) { while (index < i - start && g_fakeArgv[index] == ' ') index++; // ignore spaces // is this field a group of words between quotes or a single word ? if (g_fakeArgv[index] == '"') { index++; // move one step further to bypass the quote while (index < i - start && g_fakeArgv[index] != '"') index++; // reach end of field index++; // move one step further to bypass the quote } else while (index < i - start && g_fakeArgv[index] != ' ') index++; // this is a single word, so reach the end of field g_fakeArgc++; // we have processed one argument more } // tell now the MOD DLL to execute this ClientCommand... MDLL_ClientCommand (fakeClient); } g_fakeArgv[0] = 0; // when it's done, reset the g_fakeArgv field g_isFakeCommand = false; // reset the "fakeclient command" flag g_fakeArgc = 0; // and the argument count } const char *GetField (const char *string, int fieldId, bool endLine) { // This function gets and returns a particuliar field in a string where several szFields are // concatenated. Fields can be words, or groups of words between quotes ; separators may be // white space or tabs. A purpose of this function is to provide bots with the same Cmd_Argv // convenience the engine provides to real clients. This way the handling of real client // commands and bot client commands is exactly the same, just have a look in engine.cpp // for the hooking of pfnCmd_Argc, pfnCmd_Args and pfnCmd_Argv, which redirects the call // either to the actual engine functions (when the caller is a real client), either on // our function here, which does the same thing, when the caller is a bot. static char field[256]; // reset the string memset (field, 0, sizeof (field)); int length, i, index = 0, fieldCount = 0, start, stop; field[0] = 0; // reset field length = strlen (string); // get length of string // while we have not reached end of line while (index < length && fieldCount <= fieldId) { while (index < length && (string[index] == ' ' || string[index] == '\t')) index++; // ignore spaces or tabs // is this field multi-word between quotes or single word ? if (string[index] == '"') { index++; // move one step further to bypass the quote start = index; // save field start position while ((index < length) && (string[index] != '"')) index++; // reach end of field stop = index - 1; // save field stop position index++; // move one step further to bypass the quote } else { start = index; // save field start position while (index < length && (string[index] != ' ' && string[index] != '\t')) index++; // reach end of field stop = index - 1; // save field stop position } // is this field we just processed the wanted one ? if (fieldCount == fieldId) { for (i = start; i <= stop; i++) field[i - start] = string[i]; // store the field value in a string field[i - start] = 0; // terminate the string break; // and stop parsing } fieldCount++; // we have parsed one field more } if (endLine) field[strlen (field) - 1] = 0; strtrim (field); return (&field[0]); // returns the wanted field } void strtrim (char *string) { char *ptr = string; int length = 0, toggleFlag = 0, increment = 0; int i = 0; while (*ptr++) length++; for (i = length - 1; i >= 0; i--) { if (!isspace (string[i])) break; else { string[i] = 0; length--; } } for (i = 0; i < length; i++) { if (isspace (string[i]) && !toggleFlag) { increment++; if (increment + i < length) string[i] = string[increment + i]; } else { if (!toggleFlag) toggleFlag = 1; if (increment) string[i] = string[increment + i]; } } string[length] = 0; } const char *GetModName (void) { static char modName[256]; GET_GAME_DIR (modName); // ask the engine for the MOD directory path int length = strlen (modName); // get the length of the returned string // format the returned string to get the last directory name int stop = length - 1; while ((modName[stop] == '\\' || modName[stop] == '/') && stop > 0) stop--; // shift back any trailing separator int start = stop; while (modName[start] != '\\' && modName[start] != '/' && start > 0) start--; // shift back to the start of the last subdirectory name if (modName[start] == '\\' || modName[start] == '/') start++; // if we reached a separator, step over it // now copy the formatted string back onto itself character per character for (length = start; length <= stop; length++) modName[length - start] = modName[length]; modName[length - start] = 0; // terminate the string return &modName[0]; } // Create a directory tree void CreatePath (char *path) { for (char *ofs = path + 1 ; *ofs ; ofs++) { if (*ofs == '/') { // create the directory *ofs = 0; #ifdef PLATFORM_WIN32 mkdir (path); #else mkdir (path, 0777); #endif *ofs = '/'; } } #ifdef PLATFORM_WIN32 mkdir (path); #else mkdir (path, 0777); #endif } void DrawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, int red, int green, int blue, int brightness, int speed, int life) { // this function draws a line visible from the client side of the player whose player entity // is pointed to by ent, from the vector location start to the vector location end, // which is supposed to last life tenths seconds, and having the color defined by RGB. if (!IsValidPlayer (ent)) return; // reliability check MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, ent); WRITE_BYTE (TE_BEAMPOINTS); WRITE_COORD (start.x); WRITE_COORD (start.y); WRITE_COORD (start.z); WRITE_COORD (end.x); WRITE_COORD (end.y); WRITE_COORD (end.z); WRITE_SHORT (g_modelIndexLaser); WRITE_BYTE (0); // framestart WRITE_BYTE (10); // framerate WRITE_BYTE (life); // life in 0.1's WRITE_BYTE (width); // width WRITE_BYTE (noise); // noise WRITE_BYTE (red); // r, g, b WRITE_BYTE (green); // r, g, b WRITE_BYTE (blue); // r, g, b WRITE_BYTE (brightness); // brightness WRITE_BYTE (speed); // speed MESSAGE_END (); } void DrawArrow (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, int red, int green, int blue, int brightness, int speed, int life) { // this function draws a arrow visible from the client side of the player whose player entity // is pointed to by ent, from the vector location start to the vector location end, // which is supposed to last life tenths seconds, and having the color defined by RGB. if (!IsValidPlayer (ent)) return; // reliability check MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, ent); WRITE_BYTE (TE_BEAMPOINTS); WRITE_COORD (end.x); WRITE_COORD (end.y); WRITE_COORD (end.z); WRITE_COORD (start.x); WRITE_COORD (start.y); WRITE_COORD (start.z); WRITE_SHORT (g_modelIndexArrow); WRITE_BYTE (0); // framestart WRITE_BYTE (10); // framerate WRITE_BYTE (life); // life in 0.1's WRITE_BYTE (width); // width WRITE_BYTE (noise); // noise WRITE_BYTE (red); // r, g, b WRITE_BYTE (green); // r, g, b WRITE_BYTE (blue); // r, g, b WRITE_BYTE (brightness); // brightness WRITE_BYTE (speed); // speed MESSAGE_END (); } void UpdateGlobalExperienceData (void) { // this function called after each end of the round to update knowledge about most dangerous waypoints for each team. // no waypoints, no experience used or waypoints edited or being edited? if (g_numWaypoints < 1 || g_waypointsChanged) return; // no action unsigned short maxDamage; // maximum damage unsigned short actDamage; // actual damage int bestIndex; // best index to store bool recalcKills = false; // get the most dangerous waypoint for this position for terrorist team for (int i = 0; i < g_numWaypoints; i++) { maxDamage = 0; bestIndex = -1; for (int j = 0; j < g_numWaypoints; j++) { if (i == j) continue; actDamage = (g_experienceData + (i * g_numWaypoints) + j)->team0Damage; if (actDamage > maxDamage) { maxDamage = actDamage; bestIndex = j; } } if (maxDamage > MAX_DAMAGE_VALUE) recalcKills = true; (g_experienceData + (i * g_numWaypoints) + i)->team0DangerIndex = static_cast (bestIndex); } // get the most dangerous waypoint for this position for counter-terrorist team for (int i = 0; i < g_numWaypoints; i++) { maxDamage = 0; bestIndex = -1; for (int j = 0; j < g_numWaypoints; j++) { if (i == j) continue; actDamage = (g_experienceData + (i * g_numWaypoints) + j)->team1Damage; if (actDamage > maxDamage) { maxDamage = actDamage; bestIndex = j; } } if (maxDamage > MAX_DAMAGE_VALUE) recalcKills = true; (g_experienceData + (i * g_numWaypoints) + i)->team1DangerIndex = static_cast (bestIndex); } // adjust values if overflow is about to happen if (recalcKills) { for (int i = 0; i < g_numWaypoints; i++) { for (int j = 0; j < g_numWaypoints; j++) { if (i == j) continue; int clip = (g_experienceData + (i * g_numWaypoints) + j)->team0Damage; clip -= static_cast (MAX_DAMAGE_VALUE * 0.5); if (clip < 0) clip = 0; (g_experienceData + (i * g_numWaypoints) + j)->team0Damage = static_cast (clip); clip = (g_experienceData + (i * g_numWaypoints) + j)->team1Damage; clip -= static_cast (MAX_DAMAGE_VALUE * 0.5); if (clip < 0) clip = 0; (g_experienceData + (i * g_numWaypoints) + j)->team1Damage = static_cast (clip); } } } g_highestKills++; int clip = g_highestDamageT - static_cast (MAX_DAMAGE_VALUE * 0.5); if (clip < 1) clip = 1; g_highestDamageT = clip; clip = (int) g_highestDamageCT - static_cast (MAX_DAMAGE_VALUE * 0.5); if (clip < 1) clip = 1; g_highestDamageCT = clip; if (g_highestKills == MAX_KILL_HISTORY) { for (int i = 0; i < g_numWaypoints; i++) { (g_experienceData + (i * g_numWaypoints) + i)->team0Damage /= static_cast (GetMaxClients () * 0.5); (g_experienceData + (i * g_numWaypoints) + i)->team1Damage /= static_cast (GetMaxClients () * 0.5); } g_highestKills = 1; } } void RoundInit (void) { // this is called at the start of each round g_roundEnded = false; // check team economics g_botManager->CheckTeamEconomics (TEAM_TF); g_botManager->CheckTeamEconomics (TEAM_CF); for (int i = 0; i < GetMaxClients (); i++) { if (g_botManager->GetBot (i)) g_botManager->GetBot (i)->NewRound (); g_radioSelect[i] = 0; } g_waypoint->SetBombPosition (true); g_waypoint->ClearGoalScore (); g_bombSayString = false; g_timeBombPlanted = 0.0; g_timeNextBombUpdate = 0.0; g_leaderChoosen[TEAM_CF] = false; g_leaderChoosen[TEAM_TF] = false; g_lastRadioTime[0] = 0.0; g_lastRadioTime[1] = 0.0; g_botsCanPause = false; for (int i = 0; i < TASK_MAX; i++) g_taskFilters[i].time = 0.0f; UpdateGlobalExperienceData (); // update experience data on round start // calculate the round mid/end in world time g_timeRoundStart = GetWorldTime () + mp_freezetime.GetFloat (); g_timeRoundMid = g_timeRoundStart + mp_roundtime.GetFloat () * 60 * 0.5f; g_timeRoundEnd = g_timeRoundStart + mp_roundtime.GetFloat () * 60; } int GetWeaponPenetrationPower (int id) { // returns if weapon can pierce through a wall int i = 0; while (g_weaponSelect[i].id) { if (g_weaponSelect[i].id == id) return g_weaponSelect[i].penetratePower; i++; } return 0; } bool IsValidPlayer (edict_t *ent) { if (IsEntityNull (ent)) return false; if (ent->v.flags & FL_PROXY) return false; if ((ent->v.flags & (FL_CLIENT | FL_FAKECLIENT)) || g_botManager->GetBot (ent) != NULL) return !IsNullString (STRING (ent->v.netname)); return false; } bool IsPlayerVIP (edict_t *ent) { if (!(g_mapType & MAP_AS)) return false; if (!IsValidPlayer (ent)) return false; return *(INFOKEY_VALUE (GET_INFOKEYBUFFER (ent), "model")) == 'v'; } bool IsValidBot (edict_t *ent) { if (g_botManager->GetBot (ent) != NULL || (!IsEntityNull (ent) && (ent->v.flags & FL_FAKECLIENT))) return true; return false; } bool IsDedicatedServer (void) { // return true if server is dedicated server, false otherwise return (IS_DEDICATED_SERVER () > 0); // ask engine for this } bool TryFileOpen (const char *fileName) { // this function tests if a file exists by attempting to open it File fp; // check if got valid handle if (fp.Open (fileName, "rb")) { fp.Close (); return true; } return false; } void ServerPrint (const char *format, ...) { va_list ap; char string[3072]; va_start (ap, format); vsprintf (string, g_localizer->TranslateInput (format), ap); va_end (ap); SERVER_PRINT (string); SERVER_PRINT ("\n"); } void CenterPrint (const char *format, ...) { va_list ap; char string[2048]; va_start (ap, format); vsprintf (string, g_localizer->TranslateInput (format), ap); va_end (ap); if (IsDedicatedServer ()) { ServerPrint (string); return; } MESSAGE_BEGIN (MSG_BROADCAST, g_netMsg->GetId (NETMSG_TEXTMSG)); WRITE_BYTE (HUD_PRINTCENTER); WRITE_STRING (FormatBuffer ("%s\n", string)); MESSAGE_END (); } void ChartPrint (const char *format, ...) { va_list ap; char string[2048]; va_start (ap, format); vsprintf (string, g_localizer->TranslateInput (format), ap); va_end (ap); if (IsDedicatedServer ()) { ServerPrint (string); return; } strcat (string, "\n"); MESSAGE_BEGIN (MSG_BROADCAST, g_netMsg->GetId (NETMSG_TEXTMSG)); WRITE_BYTE (HUD_PRINTTALK); WRITE_STRING (string); MESSAGE_END (); } void ClientPrint (edict_t *ent, int dest, const char *format, ...) { va_list ap; char string[2048]; va_start (ap, format); vsprintf (string, g_localizer->TranslateInput (format), ap); va_end (ap); if (IsEntityNull (ent) || ent == g_hostEntity) { ServerPrint (string); return; } strcat (string, "\n"); (*g_engfuncs.pfnClientPrintf) (ent, static_cast (dest), string); } void ServerCommand (const char *format, ...) { // this function asks the engine to execute a server command va_list ap; static char string[1024]; // concatenate all the arguments in one string va_start (ap, format); vsprintf (string, format, ap); va_end (ap); SERVER_COMMAND (const_cast (FormatBuffer ("%s\n", string))); // execute command } const char *GetMapName (void) { // this function gets the map name and store it in the map_name global string variable. static char mapName[256]; strncpy (mapName, const_cast (g_pGlobals->pStringBase + static_cast (g_pGlobals->mapname)), SIZEOF_CHAR (mapName)); return &mapName[0]; // and return a pointer to it } extern bool OpenConfig(const char *fileName, const char *errorIfNotExists, File *outFile, bool languageDependant /*= false*/) { if (outFile->IsValid ()) outFile->Close (); if (languageDependant) { extern ConVar yb_language; if (strcmp (fileName, "lang.cfg") == 0 && strcmp (yb_language.GetString (), "en") == 0) return false; const char *languageDependantConfigFile = FormatBuffer ("%s/addons/yapb/conf/lang/%s_%s", GetModName (), yb_language.GetString (), fileName); // check is file is exists for this language if (TryFileOpen (languageDependantConfigFile)) outFile->Open (languageDependantConfigFile, "rt"); else outFile->Open (FormatBuffer ("%s/addons/yapb/conf/lang/en_%s", GetModName (), fileName), "rt"); } else outFile->Open (FormatBuffer ("%s/addons/yapb/conf/%s", GetModName (), fileName), "rt"); if (!outFile->IsValid ()) { AddLogEntry (true, LL_ERROR, errorIfNotExists); return false; } return true; } const char *GetWaypointDir (void) { return FormatBuffer ("%s/addons/yapb/data/", GetModName ()); } extern void RegisterCommand(const char *command, void funcPtr (void)) { // this function tells the engine that a new server command is being declared, in addition // to the standard ones, whose name is command_name. The engine is thus supposed to be aware // that for every "command_name" server command it receives, it should call the function // pointed to by "function" in order to handle it. if (IsNullString (command) || funcPtr == NULL) return; // reliability check REG_SVR_COMMAND (const_cast (command), funcPtr); // ask the engine to register this new command } void CheckWelcomeMessage (void) { // the purpose of this function, is to send quick welcome message, to the listenserver entity. static bool isReceived = false; static float receiveTime = 0.0; if (isReceived || !yb_listenserver_welcome.GetBool ()) return; Array sentences; // add default messages sentences.Push ("hello user,communication is acquired"); sentences.Push ("your presence is acknowledged"); sentences.Push ("high man, your in command now"); sentences.Push ("blast your hostile for good"); sentences.Push ("high man, kill some idiot here"); sentences.Push ("is there a doctor in the area"); sentences.Push ("warning, experimental materials detected"); sentences.Push ("high amigo, shoot some but"); sentences.Push ("attention, hours of work software, detected"); sentences.Push ("time for some bad ass explosion"); sentences.Push ("bad ass son of a breach device activated"); sentences.Push ("high, do not question this great service"); sentences.Push ("engine is operative, hello and goodbye"); sentences.Push ("high amigo, your administration has been great last day"); sentences.Push ("attention, expect experimental armed hostile presence"); sentences.Push ("warning, medical attention required"); if (IsAlive (g_hostEntity) && !isReceived && receiveTime < 1.0 && (g_numWaypoints > 0 ? g_isCommencing : true)) receiveTime = GetWorldTime () + 4.0; // receive welcome message in four seconds after game has commencing if (receiveTime > 0.0 && receiveTime < GetWorldTime () && !isReceived && (g_numWaypoints > 0 ? g_isCommencing : true)) { ServerCommand ("speak \"%s\"", const_cast (sentences.GetRandomElement ().GetBuffer ())); ChartPrint ("----- YaPB v%s (Build: %u), {%s}, (c) 2015, by %s -----", PRODUCT_VERSION, GenerateBuildNumber (), PRODUCT_DATE, PRODUCT_AUTHOR); MESSAGE_BEGIN (MSG_ONE, SVC_TEMPENTITY, NULL, g_hostEntity); WRITE_BYTE (TE_TEXTMESSAGE); WRITE_BYTE (1); WRITE_SHORT (FixedSigned16 (-1, 1 << 13)); WRITE_SHORT (FixedSigned16 (-1, 1 << 13)); WRITE_BYTE (2); WRITE_BYTE (Random.Long (33, 255)); WRITE_BYTE (Random.Long (33, 255)); WRITE_BYTE (Random.Long (33, 255)); WRITE_BYTE (0); WRITE_BYTE (Random.Long (230, 255)); WRITE_BYTE (Random.Long (230, 255)); WRITE_BYTE (Random.Long (230, 255)); WRITE_BYTE (200); WRITE_SHORT (FixedUnsigned16 (0.0078125, 1 << 8)); WRITE_SHORT (FixedUnsigned16 (2, 1 << 8)); WRITE_SHORT (FixedUnsigned16 (6, 1 << 8)); WRITE_SHORT (FixedUnsigned16 (0.1, 1 << 8)); WRITE_STRING (FormatBuffer ("\nServer is running YaPB v%s (Build: %u)\nDeveloped by %s\n\n%s", PRODUCT_VERSION, GenerateBuildNumber (), PRODUCT_AUTHOR, g_waypoint->GetInfo ())); MESSAGE_END (); receiveTime = 0.0; isReceived = true; } } void DetectCSVersion (void) { if (g_gameVersion == CSV_OLD || g_gameVersion == CSV_CZERO) return; // counter-strike 1.6 or higher (plus detects for non-steam versions of 1.5) byte *detection = (*g_engfuncs.pfnLoadFileForMe) ("events/galil.sc", NULL); if (detection != NULL) g_gameVersion = CSV_STEAM; // just to be sure else if (detection == NULL) g_gameVersion = CSV_OLD; // reset it to WON // if we have loaded the file free it if (detection != NULL) (*g_engfuncs.pfnFreeFile) (detection); } void PlaySound (edict_t *ent, const char *name) { // TODO: make this obsolete EMIT_SOUND_DYN2 (ent, CHAN_WEAPON, name, 1.0, ATTN_NORM, 0, 100); return; } float GetWaveLength (const char *fileName) { WavHeader waveHdr; memset (&waveHdr, 0, sizeof (waveHdr)); extern ConVar yb_chatter_path; File fp (FormatBuffer ("%s/%s/%s.wav", GetModName (), yb_chatter_path.GetString (), fileName), "rb"); // we're got valid handle? if (!fp.IsValid ()) return 0; if (fp.Read (&waveHdr, sizeof (WavHeader)) == 0) { AddLogEntry (true, LL_ERROR, "Wave File %s - has wrong or unsupported format", fileName); return 0; } if (strncmp (waveHdr.chunkID, "WAVE", 4) != 0) { AddLogEntry (true, LL_ERROR, "Wave File %s - has wrong wave chunk id", fileName); return 0; } fp.Close (); if (waveHdr.dataChunkLength == 0) { AddLogEntry (true, LL_ERROR, "Wave File %s - has zero length!", fileName); return 0; } // whoa, what a shit, is this working ?! char ch[32]; sprintf (ch, "0.%u", static_cast (waveHdr.dataChunkLength)); float secondLength = static_cast (waveHdr.dataChunkLength) / static_cast (waveHdr.bytesPerSecond); float milliSecondLength = atof (ch); return (secondLength == 0.0 ? milliSecondLength : secondLength); } void AddLogEntry (bool outputToConsole, int logLevel, const char *format, ...) { // this function logs a message to the message log file root directory. va_list ap; char buffer[512] = {0, }, levelString[32] = {0, }, logLine[1024] = {0, }; va_start (ap, format); vsprintf (buffer, g_localizer->TranslateInput (format), ap); va_end (ap); switch (logLevel) { case LL_DEFAULT: strcpy (levelString, "Log: "); break; case LL_WARNING: strcpy (levelString, "Warning: "); break; case LL_ERROR: strcpy (levelString, "Error: "); break; case LL_FATAL: strcpy (levelString, "Critical: "); break; } if (outputToConsole) ServerPrint ("%s%s", levelString, buffer); // now check if logging disabled if (!(logLevel & LL_IGNORE)) { extern ConVar yb_debug; if (logLevel == LL_DEFAULT && yb_debug.GetInt () < 3) return; // no log, default logging is disabled if (logLevel == LL_WARNING && yb_debug.GetInt () < 2) return; // no log, warning logging is disabled if (logLevel == LL_ERROR && yb_debug.GetInt () < 1) return; // no log, error logging is disabled } // open file in a standard stream File fp ("yapb.txt", "at"); // check if we got a valid handle if (!fp.IsValid ()) return; time_t tickTime = time (&tickTime); tm *time = localtime (&tickTime); sprintf (logLine, "[%02d:%02d:%02d] %s%s", time->tm_hour, time->tm_min, time->tm_sec, levelString, buffer); fp.Printf ("%s\n", logLine); fp.Close (); if (logLevel == LL_FATAL) { #if defined (PLATFORM_WIN32) MessageBoxA (GetActiveWindow (), buffer, "YaPB Error", MB_ICONSTOP); #else printf ("%s", buffer); #endif FreeLibraryMemory (); #if defined (PLATFORM_WIN32) _exit (1); #else exit (1); #endif } } char *Localizer::TranslateInput (const char *input) { if (IsDedicatedServer ()) return const_cast (&input[0]); static char string[1024]; const char *ptr = input + strlen (input) - 1; while (ptr > input && *ptr == '\n') ptr--; if (ptr != input) ptr++; strncpy (string, input, SIZEOF_CHAR (string)); strtrim (string); FOR_EACH_AE (m_langTab, i) { if (strcmp (string, m_langTab[i].original) == 0) { strncpy (string, m_langTab[i].translated, 1023); if (ptr != input) strncat (string, ptr, 1024 - 1 - strlen (string)); return &string[0]; } } return const_cast (&input[0]); // nothing found } bool FindNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool sameTeam, bool needBot, bool isAlive, bool needDrawn) { // this function finds nearest to to, player with set of parameters, like his // team, live status, search distance etc. if needBot is true, then pvHolder, will // be filled with bot pointer, else with edict pointer(!). edict_t *survive = NULL; // pointer to temporaly & survive entity float nearestPlayer = 4096.0; // nearest player int toTeam = GetTeam (to); for (int i = 0; i < GetMaxClients (); i++) { edict_t *ent = g_clients[i].ent; if (!(g_clients[i].flags & CF_USED) || ent == to) continue; if ((sameTeam && g_clients[i].team != toTeam) || (isAlive && !(g_clients[i].flags & CF_ALIVE)) || (needBot && !IsValidBot (ent)) || (needDrawn && (ent->v.effects & EF_NODRAW))) continue; // filter players with parameters float distance = (ent->v.origin - to->v.origin).GetLength (); if (distance < nearestPlayer && distance < searchDistance) { nearestPlayer = distance; survive = ent; } } if (IsEntityNull (survive)) return false; // nothing found // fill the holder if (needBot) *pvHolder = reinterpret_cast (g_botManager->GetBot (survive)); else *pvHolder = reinterpret_cast (survive); return true; } void SoundAttachToThreat (edict_t *ent, const char *sample, float volume) { // this function called by the sound hooking code (in emit_sound) enters the played sound into // the array associated with the entity if (IsEntityNull (ent) || IsNullString (sample)) return; // reliability check const Vector &origin = GetEntityOrigin (ent); int index = IndexOfEntity (ent) - 1; if (index < 0 || index >= GetMaxClients ()) { float nearestDistance = FLT_MAX; // loop through all players for (int i = 0; i < GetMaxClients (); i++) { if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE)) continue; float distance = (g_clients[i].ent->v.origin - origin).GetLengthSquared (); // now find nearest player if (distance < nearestDistance) { index = i; nearestDistance = distance; } } } if (strncmp ("player/bhit_flesh", sample, 17) == 0 || strncmp ("player/headshot", sample, 15) == 0) { // hit/fall sound? g_clients[index].hearingDistance = 768.0 * volume; g_clients[index].timeSoundLasting = GetWorldTime () + 0.5; g_clients[index].maxTimeSoundLasting = 0.5; g_clients[index].soundPosition = origin; } else if (strncmp ("items/gunpickup", sample, 15) == 0) { // weapon pickup? g_clients[index].hearingDistance = 768.0 * volume; g_clients[index].timeSoundLasting = GetWorldTime () + 0.5; g_clients[index].maxTimeSoundLasting = 0.5; g_clients[index].soundPosition = origin; } else if (strncmp ("weapons/zoom", sample, 12) == 0) { // sniper zooming? g_clients[index].hearingDistance = 512.0 * volume; g_clients[index].timeSoundLasting = GetWorldTime () + 0.1; g_clients[index].maxTimeSoundLasting = 0.1; g_clients[index].soundPosition = origin; } else if (strncmp ("items/9mmclip", sample, 13) == 0) { // ammo pickup? g_clients[index].hearingDistance = 512.0 * volume; g_clients[index].timeSoundLasting = GetWorldTime () + 0.1; g_clients[index].maxTimeSoundLasting = 0.1; g_clients[index].soundPosition = origin; } else if (strncmp ("hostage/hos", sample, 11) == 0) { // CT used hostage? g_clients[index].hearingDistance = 1024.0 * volume; g_clients[index].timeSoundLasting = GetWorldTime () + 5.0; g_clients[index].maxTimeSoundLasting = 0.5; g_clients[index].soundPosition = origin; } else if (strncmp ("debris/bustmetal", sample, 16) == 0 || strncmp ("debris/bustglass", sample, 16) == 0) { // broke something? g_clients[index].hearingDistance = 1024.0 * volume; g_clients[index].timeSoundLasting = GetWorldTime () + 2.0; g_clients[index].maxTimeSoundLasting = 2.0; g_clients[index].soundPosition = origin; } else if (strncmp ("doors/doormove", sample, 14) == 0) { // someone opened a door g_clients[index].hearingDistance = 1024.0 * volume; g_clients[index].timeSoundLasting = GetWorldTime () + 3.0; g_clients[index].maxTimeSoundLasting = 3.0; g_clients[index].soundPosition = origin; } #if 0 else if (strncmp ("weapons/reload", sample, 14) == 0) { // reloading ? g_clients[index].hearingDistance = 512.0 * volume; g_clients[index].timeSoundLasting = GetWorldTime () + 0.5; g_clients[index].maxTimeSoundLasting = 0.5; g_clients[index].soundPosition = origin; } #endif } void SoundSimulateUpdate (int playerIndex) { // this function tries to simulate playing of sounds to let the bots hear sounds which aren't // captured through server sound hooking InternalAssert (playerIndex >= 0); InternalAssert (playerIndex < GetMaxClients ()); if (playerIndex < 0 || playerIndex >= GetMaxClients ()) return; // reliability check edict_t *player = g_clients[playerIndex].ent; float velocity = player->v.velocity.GetLength2D (); float hearDistance = 0.0; float timeSound = 0.0; float timeMaxSound = 0.5; if (player->v.oldbuttons & IN_ATTACK) // pressed attack button? { hearDistance = 3072.0; timeSound = GetWorldTime () + 0.3; timeMaxSound = 0.3; } else if (player->v.oldbuttons & IN_USE) // pressed used button? { hearDistance = 512.0; timeSound = GetWorldTime () + 0.5; timeMaxSound = 0.5; } else if (player->v.oldbuttons & IN_RELOAD) // pressed reload button? { hearDistance = 512.0; timeSound = GetWorldTime () + 0.5; timeMaxSound = 0.5; } else if (player->v.movetype == MOVETYPE_FLY) // uses ladder? { if (fabs (player->v.velocity.z) > 50.0) { hearDistance = 1024.0; timeSound = GetWorldTime () + 0.3; timeMaxSound = 0.3; } } else { extern ConVar mp_footsteps; if (mp_footsteps.GetBool ()) { // moves fast enough? hearDistance = 1280.0 * (velocity / 240); timeSound = GetWorldTime () + 0.3; timeMaxSound = 0.3; } } if (hearDistance <= 0.0) return; // didn't issue sound? // some sound already associated if (g_clients[playerIndex].timeSoundLasting > GetWorldTime ()) { // new sound louder (bigger range) than old one ? if (g_clients[playerIndex].maxTimeSoundLasting <= 0.0) g_clients[playerIndex].maxTimeSoundLasting = 0.5; if (g_clients[playerIndex].hearingDistance * (g_clients[playerIndex].timeSoundLasting - GetWorldTime ()) / g_clients[playerIndex].maxTimeSoundLasting <= hearDistance) { // override it with new g_clients[playerIndex].hearingDistance = hearDistance; g_clients[playerIndex].timeSoundLasting = timeSound; g_clients[playerIndex].maxTimeSoundLasting = timeMaxSound; g_clients[playerIndex].soundPosition = player->v.origin; } } else { // just remember it g_clients[playerIndex].hearingDistance = hearDistance; g_clients[playerIndex].timeSoundLasting = timeSound; g_clients[playerIndex].maxTimeSoundLasting = timeMaxSound; g_clients[playerIndex].soundPosition = player->v.origin; } } uint16 GenerateBuildNumber (void) { // this function generates build number from the compiler date macros // get compiling date using compiler macros const char *date = __DATE__; // array of the month names const char *months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; // array of the month days byte monthDays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int day = 0; // day of the year int year = 0; // year int i = 0; // go through all monthes, and calculate, days since year start for (i = 0; i < 11; i++) { if (strncmp (&date[0], months[i], 3) == 0) break; // found current month break day += monthDays[i]; // add month days } day += atoi (&date[4]) - 1; // finally calculate day year = atoi (&date[7]) - 2000; // get years since year 2000 uint16 buildNumber = day + static_cast ((year - 1) * 365.25); // if the year is a leap year? if ((year % 4) == 0 && i > 1) buildNumber += 1; // add one year more return buildNumber - 1114; } int GetWeaponReturn (bool needString, const char *weaponAlias, int weaponIndex) { // this function returning weapon id from the weapon alias and vice versa. // structure definition for weapon tab struct WeaponTab_t { Weapon weaponIndex; // weapon id const char *alias; // weapon alias }; // weapon enumeration WeaponTab_t weaponTab[] = { {WEAPON_USP, "usp"}, // HK USP .45 Tactical {WEAPON_GLOCK, "glock"}, // Glock18 Select Fire {WEAPON_DEAGLE, "deagle"}, // Desert Eagle .50AE {WEAPON_P228, "p228"}, // SIG P228 {WEAPON_ELITE, "elite"}, // Dual Beretta 96G Elite {WEAPON_FIVESEVEN, "fn57"}, // FN Five-Seven {WEAPON_M3, "m3"}, // Benelli M3 Super90 {WEAPON_XM1014, "xm1014"}, // Benelli XM1014 {WEAPON_MP5, "mp5"}, // HK MP5-Navy {WEAPON_TMP, "tmp"}, // Steyr Tactical Machine Pistol {WEAPON_P90, "p90"}, // FN P90 {WEAPON_MAC10, "mac10"}, // Ingram MAC-10 {WEAPON_UMP45, "ump45"}, // HK UMP45 {WEAPON_AK47, "ak47"}, // Automat Kalashnikov AK-47 {WEAPON_GALIL, "galil"}, // IMI Galil {WEAPON_FAMAS, "famas"}, // GIAT FAMAS {WEAPON_SG552, "sg552"}, // Sig SG-552 Commando {WEAPON_M4A1, "m4a1"}, // Colt M4A1 Carbine {WEAPON_AUG, "aug"}, // Steyr Aug {WEAPON_SCOUT, "scout"}, // Steyr Scout {WEAPON_AWP, "awp"}, // AI Arctic Warfare/Magnum {WEAPON_G3SG1, "g3sg1"}, // HK G3/SG-1 Sniper Rifle {WEAPON_SG550, "sg550"}, // Sig SG-550 Sniper {WEAPON_M249, "m249"}, // FN M249 Para {WEAPON_FLASHBANG, "flash"}, // Concussion Grenade {WEAPON_EXPLOSIVE, "hegren"}, // High-Explosive Grenade {WEAPON_SMOKE, "sgren"}, // Smoke Grenade {WEAPON_ARMOR, "vest"}, // Kevlar Vest {WEAPON_ARMORHELM, "vesthelm"}, // Kevlar Vest and Helmet {WEAPON_DEFUSER, "defuser"}, // Defuser Kit {WEAPON_SHIELD, "shield"}, // Tactical Shield }; // if we need to return the string, find by weapon id if (needString && weaponIndex != -1) { for (int i = 0; i < ARRAYSIZE_HLSDK (weaponTab); i++) { if (weaponTab[i].weaponIndex == weaponIndex) // is weapon id found? return MAKE_STRING (weaponTab[i].alias); } return MAKE_STRING ("(none)"); // return none } // else search weapon by name and return weapon id for (int i = 0; i < ARRAYSIZE_HLSDK (weaponTab); i++) { if (strncmp (weaponTab[i].alias, weaponAlias, strlen (weaponTab[i].alias)) == 0) return weaponTab[i].weaponIndex; } return -1; // no weapon was found return -1 }