diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..1682069 --- /dev/null +++ b/include/config.h @@ -0,0 +1,229 @@ +// +// 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: +// https://yapb.ru/license +// + +#pragma once + +// botname structure definition +struct BotName { + String name = ""; + int usedBy = -1; + +public: + BotName () = default; + BotName (String &name, int usedBy) : name (cr::move (name)), usedBy (usedBy) { } +}; + +// voice config structure definition +struct ChatterItem { + String name; + float repeat; + float duration; + +public: + ChatterItem (String name, float repeat, float duration) : name (cr::move (name)), repeat (repeat), duration (duration) { } +}; + +// language hasher +struct HashLangString { + uint32 operator () (const String &key) const { + auto str = reinterpret_cast (const_cast (key.chars ())); + uint32 hash = 0; + + while (*str++) { + if (!isalnum (*str)) { + continue; + } + hash = ((*str << 5) + hash) + *str; + } + return hash; + } +}; + +// mostly config stuff, and some stuff dealing with menus +class BotConfig final : public Singleton { +private: + Array m_chat; + Array > m_chatter; + + Array m_botNames; + Array m_replies; + SmallArray m_weapons; + SmallArray m_weaponProps; + + StringArray m_logos; + StringArray m_avatars; + + Dictionary m_language; + + // default tables for personality weapon preferences, overridden by general.cfg + SmallArray m_normalWeaponPrefs = { 0, 2, 1, 4, 5, 6, 3, 12, 10, 24, 25, 13, 11, 8, 7, 22, 23, 18, 21, 17, 19, 15, 17, 9, 14, 16 }; + SmallArray m_rusherWeaponPrefs = { 0, 2, 1, 4, 5, 6, 3, 24, 19, 22, 23, 20, 21, 10, 12, 13, 7, 8, 11, 9, 18, 17, 19, 25, 15, 16 }; + SmallArray m_carefulWeaponPrefs = { 0, 2, 1, 4, 25, 6, 3, 7, 8, 12, 10, 13, 11, 9, 24, 18, 14, 17, 16, 15, 19, 20, 21, 22, 23, 5 }; + SmallArray m_botBuyEconomyTable = { 1900, 2100, 2100, 4000, 6000, 7000, 16000, 1200, 800, 1000, 3000 }; + SmallArray m_grenadeBuyPrecent = { 95, 85, 60 }; + +public: + BotConfig (); + ~BotConfig () = default; + +public: + + // load the configuration files + void loadConfigs (); + + // loads main config file + void loadMainConfig (); + + // loads bot names + void loadNamesConfig (); + + // loads weapons config + void loadWeaponsConfig (); + + // loads chatter config + void loadChatterConfig (); + + // loads chat config + void loadChatConfig (); + + // loads language config + void loadLanguageConfig (); + + // load bots logos config + void loadLogosConfig (); + + // load bots avatars config + void loadAvatarsConfig (); + + // sets memfile to use engine functions + void setupMemoryFiles (); + + // picks random bot name + BotName *pickBotName (); + + // remove bot name from used list + void clearUsedName (Bot *bot); + + // initialize weapon info + void initWeapons (); + + // fix weapon prices (ie for elite) + void adjustWeaponPrices (); + + // find weapon info by weaponi d + WeaponInfo &findWeaponById (int id); + + // translates bot message into needed language + const char *translate (const char *input); + +private: + bool isCommentLine (const String &line) { + const char ch = line.at (0); + return ch == '#' || ch == '/' || ch == '\r' || ch == ';' || ch == 0 || ch == ' '; + }; + +public: + + // checks whether chat banks contains messages + bool hasChatBank (int chatType) const { + return !m_chat[chatType].empty (); + } + + // checks whether chatter banks contains messages + bool hasChatterBank (int chatterType) const { + return !m_chatter[chatterType].empty (); + } + + // pick random phrase from chat bank + const String &pickRandomFromChatBank (int chatType) { + return m_chat[chatType].random (); + } + + // pick random phrase from chatter bank + const ChatterItem &pickRandomFromChatterBank (int chatterType) { + return m_chatter[chatterType].random (); + } + + // gets chatter repeat-interval + float getChatterMessageRepeatInterval (int chatterType) const { + return m_chatter[chatterType][0].repeat; + } + + // get's the replies array + Array &getReplies () { + return m_replies; + } + + // get's the weapon info data + SmallArray &getWeapons () { + return m_weapons; + } + + // get's raw weapon info + WeaponInfo *getRawWeapons () { + return m_weapons.begin (); + } + + // set's the weapon properties + void setWeaponProp (WeaponProp prop) { + m_weaponProps[prop.id] = cr::move (prop); + } + + // get's the weapons prop + const WeaponProp &getWeaponProp (int id) const { + return m_weaponProps[id]; + } + + // get's weapon preferences for personality + int32 *getWeaponPrefs (int personality) const { + switch (personality) { + case Personality::Normal: + default: + return m_normalWeaponPrefs.data (); + + case Personality::Rusher: + return m_rusherWeaponPrefs.data (); + + case Personality::Careful: + return m_carefulWeaponPrefs.data (); + } + } + + // get economics value + int32 *getEconLimit () { + return m_botBuyEconomyTable.data (); + } + + // get's grenade buy percents + bool chanceToBuyGrenade (int grenadeType) const { + return rg.chance (m_grenadeBuyPrecent[grenadeType]); + } + + // get's random avatar for player (if any) + String getRandomAvatar () const { + if (!m_avatars.empty ()) { + return m_avatars.random (); + } + return ""; + } + + // get's random logo index + int getRandomLogoIndex () const { + return m_logos.index (m_logos.random ()); + } + + // get random name by index + const String &getRandomLogoName (int index) const { + return m_logos[index]; + } +}; + + +// explose global +static auto &conf = BotConfig::get (); \ No newline at end of file diff --git a/include/control.h b/include/control.h new file mode 100644 index 0000000..18fcd98 --- /dev/null +++ b/include/control.h @@ -0,0 +1,215 @@ +// +// 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: +// https://yapb.ru/license +// + +#pragma once + +// command handler status +CR_DECLARE_SCOPED_ENUM (BotCommandResult, + Handled = 0, // command successfully handled + ListenServer, // command is only avaialble on listen server + BadFormat // wrong params +) + +// bot command manager +class BotControl final : public Singleton { +public: + using Handler = int (BotControl::*) (); + using MenuHandler = int (BotControl::*) (int); + +public: + // generic bot command + struct BotCmd { + String name, format, help; + Handler handler = nullptr; + + public: + BotCmd () = default; + BotCmd (String name, String format, String help, Handler handler) : name (cr::move (name)), format (cr::move (format)), help (cr::move (help)), handler (cr::move (handler)) { } + }; + + // single bot menu + struct BotMenu { + int ident, slots; + String text; + MenuHandler handler; + + public: + BotMenu (int ident, int slots, String text, MenuHandler handler) : ident (ident), slots (slots), text (cr::move (text)), handler (cr::move (handler)) { } + }; + +private: + StringArray m_args; + Array m_cmds; + Array m_menus; + + edict_t *m_ent; + + bool m_isFromConsole; + bool m_rapidOutput; + bool m_isMenuFillCommand; + int m_menuServerFillTeam; + int m_interMenuData[4] = { 0, }; + +public: + BotControl (); + ~BotControl () = default; + +private: + int cmdAddBot (); + int cmdKickBot (); + int cmdKickBots (); + int cmdKillBots (); + int cmdFill (); + int cmdVote (); + int cmdWeaponMode (); + int cmdVersion (); + int cmdNodeMenu (); + int cmdMenu (); + int cmdList (); + int cmdNode (); + int cmdNodeOn (); + int cmdNodeOff (); + int cmdNodeAdd (); + int cmdNodeAddBasic (); + int cmdNodeSave (); + int cmdNodeLoad (); + int cmdNodeErase (); + int cmdNodeDelete (); + int cmdNodeCheck (); + int cmdNodeCache (); + int cmdNodeClean (); + int cmdNodeSetRadius (); + int cmdNodeSetFlags (); + int cmdNodeTeleport (); + int cmdNodePathCreate (); + int cmdNodePathDelete (); + int cmdNodePathSetAutoDistance (); + int cmdNodeAcquireEditor (); + int cmdNodeReleaseEditor (); + int cmdNodeUpload (); + +private: + int menuMain (int item); + int menuFeatures (int item); + int menuControl (int item); + int menuWeaponMode (int item); + int menuPersonality (int item); + int menuDifficulty (int item); + int menuTeamSelect (int item); + int menuClassSelect (int item); + int menuCommands (int item); + int menuGraphPage1 (int item); + int menuGraphPage2 (int item); + int menuGraphRadius (int item); + int menuGraphType (int item); + int menuGraphFlag (int item); + int menuGraphPath (int item); + int menuAutoPathDistance (int item); + int menuKickPage1 (int item); + int menuKickPage2 (int item); + int menuKickPage3 (int item); + int menuKickPage4 (int item); + +private: + void enableDrawModels (bool enable); + void createMenus (); + +public: + bool executeCommands (); + bool executeMenus (); + + void showMenu (int id); + void kickBotByMenu (int page); + void assignAdminRights (edict_t *ent, char *infobuffer); + void maintainAdminRights (); + +public: + void setFromConsole (bool console) { + m_isFromConsole = console; + } + + void setRapidOutput (bool force) { + m_rapidOutput = force; + } + + void setIssuer (edict_t *ent) { + m_ent = ent; + } + + void fixMissingArgs (size_t num) { + if (num < m_args.length ()) { + return; + } + m_args.resize (num); + } + + int getInt (size_t arg) const { + if (!hasArg (arg)) { + return 0; + } + return m_args[arg].int_ (); + } + + const String &getStr (size_t arg) { + static String empty ("empty"); + + if (!hasArg (arg) || m_args[arg].empty ()) { + return empty; + } + return m_args[arg]; + } + + bool hasArg (size_t arg) const { + return arg < m_args.length (); + } + + void collectArgs () { + m_args.clear (); + + for (int i = 0; i < engfuncs.pfnCmd_Argc (); ++i) { + m_args.emplace (engfuncs.pfnCmd_Argv (i)); + } + } + + // global heloer for sending message to correct channel + template void msg (const char *fmt, Args ...args); + +public: + + // for the server commands + static void handleEngineCommands (); + + // for the client commands + bool handleClientCommands (edict_t *ent); + + // for the client menu commands + bool handleMenuCommands (edict_t *ent); +}; + +// global heloer for sending message to correct channel +template inline void BotControl::msg (const char *fmt, Args ...args) { + auto result = strings.format (fmt, cr::forward (args)...); + + // if no receiver or many message have to appear, just print to server console + if (game.isNullEntity (m_ent) || m_rapidOutput) { + game.print (result); + return; + } + + if (m_isFromConsole || strlen (result) > 48) { + game.clientPrint (m_ent, result); + } + else { + game.centerPrint (m_ent, result); + game.clientPrint (m_ent, result); + } +} + +// explose global +static auto &ctrl = BotControl::get (); \ No newline at end of file diff --git a/include/crlib/cr-array.h b/include/crlib/cr-array.h index ad602c5..cf1ba37 100644 --- a/include/crlib/cr-array.h +++ b/include/crlib/cr-array.h @@ -50,7 +50,7 @@ public: rhs.reset (); } - Array (std::initializer_list list) { + Array (const std::initializer_list &list) { for (const auto &elem : list) { push (elem); } diff --git a/include/crlib/cr-dict.h b/include/crlib/cr-dict.h index 28ade6c..7515d5b 100644 --- a/include/crlib/cr-dict.h +++ b/include/crlib/cr-dict.h @@ -40,6 +40,13 @@ template struct IntHash { } }; +// template for np hashing integers +template struct IntNoHash { + uint32 operator () (K key) const { + return static_cast (key); + } +}; + namespace detail { struct DictionaryList { uint32 index; diff --git a/include/crlib/cr-string.h b/include/crlib/cr-string.h index cc125a3..c609b4d 100644 --- a/include/crlib/cr-string.h +++ b/include/crlib/cr-string.h @@ -862,6 +862,14 @@ public: return buffer; } + + // checks if string is not empty + bool isEmpty (const char *input) const { + if (input == nullptr) { + return true; + } + return *input == '\0'; + } }; // expose global string pool @@ -1082,14 +1090,19 @@ public: String strToUpper (const String &in) { String result (in); + auto ptr = const_cast (result.chars ()); - int32 len = 0; - wchar_t wide; - while (*ptr && len < static_cast (result.length ())) { + while (*ptr) { + wchar_t wide = 0; + multiByteToWideChar (&wide, ptr); - ptr += wideCharToMultiByte (ptr, toUpper (wide)); + len += wideCharToMultiByte (ptr, toUpper (wide)); + + if (static_cast (len) >= result.length ()) { + break; + } } return result; } diff --git a/include/crlib/cr-vector.h b/include/crlib/cr-vector.h index e7d2161..1636eb4 100644 --- a/include/crlib/cr-vector.h +++ b/include/crlib/cr-vector.h @@ -160,8 +160,8 @@ public: } static const Vector &null () { - static const auto s_zero = Vector (0.0f, 0.0f, 0.0f); - return s_zero; + static const Vector &s_null {}; + return s_null; } void clear () { @@ -224,6 +224,27 @@ public: upward->z = cosines[roll] * cosines[pitch]; } } + + const Vector &forward () { + static Vector s_fwd {}; + buildVectors (&s_fwd, nullptr, nullptr); + + return s_fwd; + } + + const Vector &upward () { + static Vector s_up {}; + buildVectors (nullptr, nullptr, &s_up); + + return s_up; + } + + const Vector &right () { + static Vector s_right {}; + buildVectors (nullptr, &s_right, nullptr); + + return s_right; + } }; // expose global null vector diff --git a/include/engine.h b/include/engine.h index 6873ef0..6af020b 100644 --- a/include/engine.h +++ b/include/engine.h @@ -33,34 +33,6 @@ CR_DECLARE_SCOPED_ENUM (Var, NoRegister ) -// netmessage functions -CR_DECLARE_SCOPED_ENUM (NetMsg, - None = -1, - VGUI = 1, - ShowMenu = 2, - WeaponList = 3, - CurWeapon = 4, - AmmoX = 5, - AmmoPickup = 6, - Damage = 7, - Money = 8, - StatusIcon = 9, - DeathMsg = 10, - ScreenFade = 11, - HLTV = 12, - TextMsg = 13, - TeamInfo = 14, - BarTime = 15, - SendAudio = 17, - SayText = 18, - BotVoice = 19, - NVGToggle = 20, - FlashBat = 21, - Fashlight = 22, - ItemStatus = 23, - Count = 25 -) - // supported cs's CR_DECLARE_SCOPED_ENUM (GameFlags, Modern = cr::bit (0), // counter-strike 1.6 and above @@ -97,19 +69,6 @@ struct VarPair { class ConVar *self; }; -// network message block -struct MessageBlock { - int bot; - int state; - int msg; - int regMsgs[NetMsg::Count]; -}; - -// referentia vector info -struct RefVector { - Vector forward, right, up; -}; - // entity prototype using EntityFunction = void (*) (entvars_t *); @@ -126,9 +85,8 @@ private: edict_t *m_startEntity; edict_t *m_localEntity; - Array m_cvars; + SmallArray m_cvars; SharedLibrary m_gameLib; - MessageBlock m_msgBlock; bool m_precached; int m_gameFlags; @@ -136,12 +94,9 @@ private: float m_slowFrame; // per second updated frame -public: - RefVector vec; - public: Game (); - ~Game (); + ~Game () = default; public: // precaches internal stuff @@ -189,9 +144,6 @@ public: // sends local registration stack for engine registration void registerCvars (bool gameVars = false); - // do actual network message processing - void processMessages (void *ptr); - // checks whether softwared rendering is enabled bool isSoftwareRenderer (); @@ -207,9 +159,6 @@ public: // executes stuff every 1 second void slowFrame (); - // begin message handler - void beginMessage (edict_t *ent, int dest, int type); - // public inlines public: // get the current time on server @@ -281,40 +230,11 @@ public: } // gets the player team - int getTeam (edict_t *ent); - - // resets the message capture mechanism - void resetMessages () { - m_msgBlock.msg = NetMsg::None; - m_msgBlock.state = 0; - m_msgBlock.bot = 0; - }; - - // sets the currently executed message - void setCurrentMessageId (int message) { - m_msgBlock.msg = message; - } - - // set the bot entity that receive this message - void setCurrentMessageOwner (int id) { - m_msgBlock.bot = id; - } - - // find registered message id - int getMessageId (int type) { - return m_msgBlock.regMsgs[type]; - } - - // assigns message id for message type - void setMessageId (int type, int id) { - m_msgBlock.regMsgs[type] = id; - } - - // tries to set needed message id - void captureMessage (int type, int msgId) { - if (type == m_msgBlock.regMsgs[msgId]) { - setCurrentMessageId (msgId); + int getTeam (edict_t *ent) { + if (isNullEntity (ent)) { + return Team::Unassigned; } + return util.getClient (indexOfPlayer (ent)).team; } // sets the precache to uninitialize @@ -332,11 +252,6 @@ public: m_localEntity = ent; } - // builds referential vector - void makeVectors (const Vector &in) { - in.buildVectors (&vec.forward, &vec.right, &vec.up); - } - // check the engine visibility wrapper bool checkVisibility (edict_t *ent, uint8 *set); @@ -401,7 +316,7 @@ public: }; // simplify access for console variables -class ConVar { +class ConVar final { public: cvar_t *eptr; @@ -439,7 +354,7 @@ public: } }; -class MessageWriter { +class MessageWriter final { private: bool m_autoDestruct { false }; diff --git a/include/graph.h b/include/graph.h new file mode 100644 index 0000000..75ca83c --- /dev/null +++ b/include/graph.h @@ -0,0 +1,436 @@ +// +// 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: +// https://yapb.ru/license +// + +#pragma once + +// defines for nodes flags field (32 bits are available) +CR_DECLARE_SCOPED_ENUM (NodeFlag, + Lift = cr::bit (1), // wait for lift to be down before approaching this node + Crouch = cr::bit (2), // must crouch to reach this node + Crossing = cr::bit (3), // a target node + Goal = cr::bit (4), // mission goal point (bomb, hostage etc.) + Ladder = cr::bit (5), // node is on ladder + Rescue = cr::bit (6), // node is a hostage rescue point + Camp = cr::bit (7), // node is a camping point + NoHostage = cr::bit (8), // only use this node if no hostage + DoubleJump = cr::bit (9), // bot help's another bot (requster) to get somewhere (using djump) + Sniper = cr::bit (28), // it's a specific sniper point + TerroristOnly = cr::bit (29), // it's a specific terrorist point + CTOnly = cr::bit (30), // it's a specific ct point +) + +// defines for node connection flags field (16 bits are available) +CR_DECLARE_SCOPED_ENUM_TYPE (PathFlag, uint16, + Jump = cr::bit (0) // must jump for this connection +) + +// enum pathfind search type +CR_DECLARE_SCOPED_ENUM (FindPath, + Fast = 0, + Optimal, + Safe +) + +// defines node connection types +CR_DECLARE_SCOPED_ENUM (PathConnection, + Outgoing = 0, + Incoming, + Bidirectional +) + +// defines node add commands +CR_DECLARE_SCOPED_ENUM (GraphAdd, + Normal = 0, +) + +// a* route state +CR_DECLARE_SCOPED_ENUM (RouteState, + Open = 0, + Closed, + New +) + +// node edit states +CR_DECLARE_SCOPED_ENUM (GraphEdit, + On = cr::bit (1), + Noclip = cr::bit (2), + Auto = cr::bit (3) +) + +// storage header options +CR_DECLARE_SCOPED_ENUM (StorageOption, + Practice = cr::bit (0), // this is practice (experience) file + Matrix = cr::bit (1), // this is floyd warshal path & distance matrix + Vistable = cr::bit (2), // this is vistable data + Graph = cr::bit (3), // this is a node graph data + Official = cr::bit (4), // this is additional flag for graph indicates graph are official + Recovered = cr::bit (5), // this is additional flag indicates graph converted from podbot and was bad + Author = cr::bit (6) // this is additional flag indicates that there's author info +) + +// storage header versions +CR_DECLARE_SCOPED_ENUM (StorageVersion, + Graph = 1, + Practice = 1, + Vistable = 1, + Matrix = 1, + Podbot = 7 +) + +// lift usage states +CR_DECLARE_SCOPED_ENUM (LiftState, + None = 0, + LookingButtonOutside, + WaitingFor, + EnteringIn, + WaitingForTeammates, + LookingButtonInside, + TravelingBy, + Leaving +) + +// a* route +struct Route { + float g, f; + int parent; + RouteState state; +}; + +// general stprage header information structure +struct StorageHeader { + int32 magic; + int32 version; + int32 options; + int32 length; + int32 compressed; + int32 uncompressed; +}; + +// general waypoint header information structure +struct PODGraphHeader { + char header[8]; + int32 fileVersion; + int32 pointNumber; + char mapName[32]; + char author[32]; +}; + +// floyd-warshall matrices +struct Matrix { + int16 dist; + int16 index; +}; + +// experience data hold in memory while playing +struct Practice { + int16 damage[kGameTeamNum]; + int16 index[kGameTeamNum]; + int16 value[kGameTeamNum]; +}; + +// defines linked waypoints +struct PathLink { + Vector velocity; + int32 distance; + uint16 flags; + int16 index; +}; + +// defines visibility count +struct PathVis { + uint16 stand, crouch; +}; + +// define graph path structure for yapb +struct Path { + int32 number, flags; + Vector origin, start, end; + float radius, light, display; + PathLink links[kMaxNodeLinks]; + PathVis vis; +}; + +// define waypoint structure for podbot (will convert on load) +struct PODPath { + int32 number, flags; + Vector origin; + float radius, csx, csy, cex, cey; + int16 index[kMaxNodeLinks]; + uint16 conflags[kMaxNodeLinks]; + Vector velocity[kMaxNodeLinks]; + int32 distance[kMaxNodeLinks]; + PathVis vis; +}; + +// this structure links nodes returned from pathfinder +class PathWalk final : public DenyCopying { +private: + size_t m_cursor = 0; + Array m_storage; + +public: + explicit PathWalk () = default; + ~PathWalk () = default; + +public: + int32 &next () { + return at (m_cursor + 1); + } + + int32 &first () { + return at (m_cursor); + } + + int32 &last () { + return m_storage.last (); + } + + int32 &at (size_t index) { + return m_storage.at (index); + } + + void shift () { + ++m_cursor; + } + + void reverse () { + m_storage.reverse (); + } + + size_t length () const { + if (m_cursor > m_storage.length ()) { + return 0; + } + return m_storage.length () - m_cursor; + } + + size_t cursor () const { + return m_cursor; + } + + bool hasNext () const { + return m_cursor < m_storage.length (); + } + + bool empty () const { + return !length (); + } + + void push (int node) { + m_storage.push (node); + } + + void clear () { + m_cursor = 0; + m_storage.clear (); + } +}; + +// graph operation class +class BotGraph final : public Singleton { +public: + friend class Bot; + +private: + struct Bucket { + int x, y, z; + }; + + int m_editFlags; + int m_loadAttempts; + int m_cacheNodeIndex; + int m_lastJumpNode; + int m_findWPIndex; + int m_facingAtIndex; + int m_highestDamage[kGameTeamNum]; + + float m_timeJumpStarted; + float m_autoPathDistance; + float m_pathDisplayTime; + float m_arrowDisplayTime; + + bool m_isOnLadder; + bool m_endJumpPoint; + bool m_jumpLearnNode; + bool m_hasChanged; + bool m_needsVisRebuild; + + Vector m_learnVelocity; + Vector m_learnPosition; + Vector m_bombPos; + Vector m_lastNode; + + IntArray m_terrorPoints; + IntArray m_ctPoints; + IntArray m_goalPoints; + IntArray m_campPoints; + IntArray m_sniperPoints; + IntArray m_rescuePoints; + IntArray m_visitedGoals; + + SmallArray m_buckets[kMaxBucketsInsidePos][kMaxBucketsInsidePos][kMaxBucketsInsidePos]; + SmallArray m_matrix; + SmallArray m_practice; + SmallArray m_paths; + SmallArray m_vistable; + + String m_tempStrings; + edict_t *m_editor; + +public: + BotGraph (); + ~BotGraph () = default; + +public: + + int getFacingIndex (); + int getFarest (const Vector &origin, float maxDistance = 32.0); + int getNearest (const Vector &origin, float minDistance = kInfiniteDistance, int flags = -1); + int getNearestNoBuckets (const Vector &origin, float minDistance = kInfiniteDistance, int flags = -1); + int getEditorNeareset (); + int getDangerIndex (int team, int start, int goal); + int getDangerValue (int team, int start, int goal); + int getDangerDamage (int team, int start, int goal); + int getPathDist (int srcIndex, int destIndex); + int clearConnections (int index); + + float calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin); + + bool convertOldFormat (); + bool isVisible (int srcIndex, int destIndex); + bool isStandVisible (int srcIndex, int destIndex); + bool isDuckVisible (int srcIndex, int destIndex); + bool isConnected (int a, int b); + bool isConnected (int index); + bool isNodeReacheable (const Vector &src, const Vector &destination); + bool checkNodes (bool teleportPlayer); + bool loadPathMatrix (); + bool isVisited (int index); + + bool saveGraphData (); + bool loadGraphData (); + + template bool saveStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, const SmallArray &data, uint8 *blob); + template bool loadStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, SmallArray &data, uint8 *blob, int32 *outOptions); + + void saveOldFormat (); + void initGraph (); + void frame (); + void loadPractice (); + void loadVisibility (); + void initNodesTypes (); + void initLightLevels (); + void addPath (int addIndex, int pathIndex, float distance); + void add (int type, const Vector &pos = nullvec); + void erase (int target); + void toggleFlags (int toggleFlag); + void setRadius (int index, float radius); + void rebuildVisibility (); + void pathCreate (char dir); + void erasePath (); + void cachePoint (int index); + void calculatePathRadius (int index); + void savePractice (); + void saveVisibility (); + void addBasic (); + void eraseFromDisk (); + void savePathMatrix (); + void setSearchIndex (int index); + void startLearnJump (); + void setVisited (int index); + void clearVisited (); + void initBuckets (); + void addToBucket (const Vector &pos, int index); + void eraseFromBucket (const Vector &pos, int index); + void setBombPos (bool reset = false, const Vector &pos = nullvec); + void updateGlobalPractice (); + void unassignPath (int from, int to); + void setDangerValue (int team, int start, int goal, int value); + void setDangerDamage (int team, int start, int goal, int value); + void convertFromPOD (Path &path, const PODPath &pod); + void convertToPOD (const Path &path, PODPath &pod); + void convertCampDirection (Path &path); + + const char *getDataDirectory (bool isMemoryFile = false); + const char *getOldFormatGraphName (bool isMemoryFile = false); + + Bucket locateBucket (const Vector &pos); + IntArray searchRadius (float radius, const Vector &origin, int maxCount = -1); + const SmallArray &getNodesInBucket (const Vector &pos); + +public: + int getHighestDamageForTeam (int team) const { + return m_highestDamage[team]; + } + + void setHighestDamageForTeam (int team, int value) { + m_highestDamage[team] = value; + } + + const char *getAuthor () const { + return m_tempStrings.chars (); + } + + bool hasChanged () const { + return m_hasChanged; + } + + bool hasEditFlag (int flag) const { + return !!(m_editFlags & flag); + } + + void setEditFlag (int flag) { + m_editFlags |= flag; + } + + void clearEditFlag (int flag) { + m_editFlags &= ~flag; + } + + void setAutoPathDistance (const float distance) { + m_autoPathDistance = distance; + } + + const Vector &getBombPos () const { + return m_bombPos; + } + + // access paths + Path &operator [] (int index) { + return m_paths[index]; + } + + // check nodes range + bool exists (int index) const { + return index >= 0 && index < static_cast (m_paths.length ()); + } + + // get real nodes num + int length () const { + return m_paths.length (); + } + + // check if has editor + bool hasEditor () const { + return !!m_editor; + } + + // set's the node editor + void setEditor (edict_t *ent) { + m_editor = ent; + } + + // get the current node editor + edict_t *getEditor () { + return m_editor; + } +}; + +// explose global +static auto &graph = BotGraph::get (); \ No newline at end of file diff --git a/include/manager.h b/include/manager.h new file mode 100644 index 0000000..704b36d --- /dev/null +++ b/include/manager.h @@ -0,0 +1,273 @@ +// +// 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: +// https://yapb.ru/license +// + +#pragma once + +// bot creation tab +struct CreateQueue { + bool manual; + int difficulty; + int team; + int member; + int personality; + String name; +}; + +// manager class +class BotManager final : public Singleton { +public: + using ForEachBot = Lambda ; + using UniqueBot = UniquePtr ; + +private: + float m_timeRoundStart; + float m_timeRoundEnd; + float m_timeRoundMid; + + float m_maintainTime; // time to maintain bot creation + float m_quotaMaintainTime; // time to maintain bot quota + float m_grenadeUpdateTime; // time to update active grenades + float m_entityUpdateTime; // time to update intresting entities + float m_plantSearchUpdateTime; // time to update for searching planted bomb + float m_lastChatTime; // global chat time timestamp + float m_timeBombPlanted; // time the bomb were planted + float m_lastRadioTime[kGameTeamNum]; // global radio time + + int m_lastWinner; // the team who won previous round + int m_lastDifficulty; // last bots difficulty + int m_bombSayStatus; // some bot is issued whine about bomb + int m_lastRadio[kGameTeamNum]; // last radio message for team + + bool m_leaderChoosen[kGameTeamNum]; // is team leader choose theese round + bool m_economicsGood[kGameTeamNum]; // is team able to buy anything + bool m_bombPlanted; + bool m_botsCanPause; + bool m_roundEnded; + + Array m_activeGrenades; // holds currently active grenades on the map + Array m_intrestingEntities; // holds currently intresting entities on the map + + SmallArray m_creationTab; // bot creation tab + SmallArray m_filters; // task filters + SmallArray m_bots; // all available bots + + edict_t *m_killerEntity; // killer entity for bots + +protected: + BotCreateResult create (const String &name, int difficulty, int personality, int team, int member); + +public: + BotManager (); + ~BotManager () = default; + +public: + Twin countTeamPlayers (); + + Bot *findBotByIndex (int index); + Bot *findBotByEntity (edict_t *ent); + + Bot *findAliveBot (); + Bot *findHighestFragBot (int team); + + int getHumansCount (bool ignoreSpectators = false); + int getAliveHumansCount (); + int getBotCount (); + float getConnectionTime (int botId); + + void setBombPlanted (bool isPlanted); + void slowFrame (); + void frame (); + void createKillerEntity (); + void destroyKillerEntity (); + void touchKillerEntity (Bot *bot); + void destroy (); + void addbot (const String &name, int difficulty, int personality, int team, int member, bool manual); + void addbot (const String &name, const String &difficulty, const String &personality, const String &team, const String &member, bool manual); + void serverFill (int selection, int personality = Personality::Normal, int difficulty = -1, int numToAdd = -1); + void kickEveryone (bool instant = false, bool zeroQuota = true); + bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned); + void kickBot (int index); + void kickFromTeam (Team team, bool removeAll = false); + void killAllBots (int team = -1); + void maintainQuota (); + void initQuota (); + void initRound (); + void decrementQuota (int by = 1); + void selectLeaders (int team, bool reset); + void listBots (); + void setWeaponMode (int selection); + void updateTeamEconomics (int team, bool setTrue = false); + void updateBotDifficulties (); + void reset (); + void initFilters (); + void resetFilters (); + void updateActiveGrenade (); + void updateIntrestingEntities (); + void captureChatRadio (const char *cmd, const char *arg, edict_t *ent); + void notifyBombDefuse (); + void execGameEntity (entvars_t *vars); + void forEach (ForEachBot handler); + void erase (Bot *bot); + void handleDeath (edict_t *killer, edict_t *victim); + + bool isTeamStacked (int team); + +public: + Array &searchActiveGrenades () { + return m_activeGrenades; + } + + Array &searchIntrestingEntities () { + return m_intrestingEntities; + } + + bool hasActiveGrenades () const { + return !m_activeGrenades.empty (); + } + + bool hasIntrestingEntities () const { + return !m_intrestingEntities.empty (); + } + + bool checkTeamEco (int team) const { + return m_economicsGood[team]; + } + + int getLastWinner () const { + return m_lastWinner; + } + + void setLastWinner (int winner) { + m_lastWinner = winner; + } + + // get the list of filters + SmallArray &getFilters () { + return m_filters; + } + + void createRandom (bool manual = false) { + addbot ("", -1, -1, -1, -1, manual); + } + + bool isBombPlanted () const { + return m_bombPlanted; + } + + float getTimeBombPlanted () const { + return m_timeBombPlanted; + } + + float getRoundStartTime () const { + return m_timeRoundStart; + } + + float getRoundMidTime () const { + return m_timeRoundMid; + } + + float getRoundEndTime () const { + return m_timeRoundEnd; + } + + bool isRoundOver () const { + return m_roundEnded; + } + + void setRoundOver (const bool over) { + m_roundEnded = over; + } + + bool canPause () const { + return m_botsCanPause; + } + + void setCanPause (const bool pause) { + m_botsCanPause = pause; + } + + bool hasBombSay (int type) { + return (m_bombSayStatus & type) == type; + } + + void clearBombSay (int type) { + m_bombSayStatus &= ~type; + } + + void setPlantedBombSearchTimestamp (const float timestamp) { + m_plantSearchUpdateTime = timestamp; + } + + float getPlantedBombSearchTimestamp () const { + return m_plantSearchUpdateTime; + } + + void setLastRadioTimestamp (const int team, const float timestamp) { + if (team == Team::CT || team == Team::Terrorist) { + m_lastRadioTime[team] = timestamp; + } + } + + float getLastRadioTimestamp (const int team) const { + if (team == Team::CT || team == Team::Terrorist) { + return m_lastRadioTime[team]; + } + return 0.0f; + } + + void setLastRadio (const int team, const int radio) { + m_lastRadio[team] = radio; + } + + int getLastRadio (const int team) const { + return m_lastRadio[team]; + } + + void setLastChatTimestamp (const float timestamp) { + m_lastChatTime = timestamp; + } + + float getLastChatTimestamp () const { + return m_lastChatTime; + } + + // some bots are online ? + bool hasBotsOnline () { + return getBotCount () > 0; + } + +public: + Bot *operator [] (int index) { + return findBotByIndex (index); + } + + Bot *operator [] (edict_t *ent) { + return findBotByEntity (ent); + } + +public: + UniqueBot *begin () { + return m_bots.begin (); + } + + UniqueBot *begin () const { + return m_bots.begin (); + } + + UniqueBot *end () { + return m_bots.end (); + } + + UniqueBot *end () const { + return m_bots.end (); + } +}; + +// explose global +static auto &bots = BotManager::get (); \ No newline at end of file diff --git a/include/message.h b/include/message.h new file mode 100644 index 0000000..91bcfc2 --- /dev/null +++ b/include/message.h @@ -0,0 +1,147 @@ +// +// 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: +// https://yapb.ru/license +// + +#pragma once + +// netmessage functions enum +CR_DECLARE_SCOPED_ENUM (NetMsg, + None = -1, + VGUIMenu = 1, + ShowMenu = 2, + WeaponList = 3, + CurWeapon = 4, + AmmoX = 5, + AmmoPickup = 6, + Damage = 7, + Money = 8, + StatusIcon = 9, + DeathMsg = 10, + ScreenFade = 11, + HLTV = 12, + TextMsg = 13, + TeamInfo = 14, + BarTime = 15, + SendAudio = 17, + BotVoice = 18, + NVGToggle = 19, + FlashBat = 20, + Fashlight = 21, + ItemStatus = 22 +) + +// vgui menus (since latest steam updates is obsolete, but left for old cs) +CR_DECLARE_SCOPED_ENUM (GuiMenu, + TeamSelect = 2, // menu select team + TerroristSelect = 26, // terrorist select menu + CTSelect = 27 // ct select menu +) + +// cache flags for TextMsg message +CR_DECLARE_SCOPED_ENUM (TextMsgCache, + NeedHandle = cr::bit (0), + TerroristWin = cr::bit (1), + CounterWin = cr::bit (2), + Commencing = cr::bit (3), + BombPlanted = cr::bit (4), + RestartRound = cr::bit (5), + BurstOn = cr::bit (6), + BurstOff = cr::bit (7) +) + +// cache flags for StatusIcon message +CR_DECLARE_SCOPED_ENUM (StatusIconCache, + NeedHandle = cr::bit (0), + BuyZone = cr::bit (1), + VipSafety = cr::bit (2), + C4 = cr::bit (3) +) + +class MessageDispatcher final : public Singleton { +private: + using MsgFunc = void (MessageDispatcher::*) (); + +private: + struct Args { + union { + float float_; + long long_; + const char *chars_; + }; + + public: + Args (float value) : float_ (value) { } + Args (int value) : long_ (value) { } + Args (const char *value) : chars_ (value) { } + }; + +private: + Dictionary m_textMsgCache; // cache strings for faster access for textmsg + Dictionary m_showMenuCache; // cache for the showmenu message + Dictionary m_statusIconCache; // cache for status icon message + Dictionary m_teamInfoCache; // cache for teaminfo message + +private: + bool m_broadcast; // message for all players + + Bot *m_bot; // owner of a message + NetMsg m_current; // ongoing message id + + SmallArray m_args; // args collected from write* functions + Dictionary m_wanted; // wanted messages + + Dictionary > m_maps; // maps our message to id to engine message id + Dictionary > m_handlers; // maps our message id to handler function + +private: + void netMsgTextMsg (); + void netMsgVGUIMenu (); + void netMsgShowMenu (); + void netMsgWeaponList (); + void netMsgCurWeapon (); + void netMsgAmmoX (); + void netMsgAmmoPickup (); + void netMsgDamage (); + void netMsgMoney (); + void netMsgStatusIcon (); + void netMsgDeathMsg (); + void netMsgScreenFade (); + void netMsgHLTV (); + void netMsgTeamInfo (); + void netMsgBarTime (); + void netMsgItemStatus (); + void netMsgNVGToggle (); + void netMsgFlashBat (); + +public: + MessageDispatcher (); + ~MessageDispatcher () = default; + +public: + void registerMessage (const String &name, int32 id); + void start (edict_t *ent, int32 dest, int32 type); + void stop (); + +public: + template void collect (const T &value) { + if (m_current == NetMsg::None) { + return; + } + m_args.emplace (value); + } + + void stopCollection () { + m_current = NetMsg::None; + } + + int32 id (NetMsg msg) const { + return m_maps[msg]; + } +}; + +static auto &msgs = MessageDispatcher::get (); \ No newline at end of file diff --git a/include/utils.h b/include/utils.h new file mode 100644 index 0000000..ecead99 --- /dev/null +++ b/include/utils.h @@ -0,0 +1,137 @@ +// +// 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: +// https://yapb.ru/license +// + +#pragma once + +class BotUtils final : public Singleton { +private: + bool m_needToSendWelcome; + float m_welcomeReceiveTime; + + StringArray m_sentences; + SmallArray m_clients; + SmallArray > m_tags; + + SimpleHook m_sendToHook; + +public: + BotUtils (); + ~BotUtils () = default; + +public: + // need to send welcome message ? + void checkWelcome (); + + // gets the weapon alias as hlsting, maybe move to config... + int getWeaponAlias (bool needString, const char *weaponAlias, int weaponIndex = -1); + + // gets the build number of bot + int buildNumber (); + + // gets the shooting cone deviation + float getShootingCone (edict_t *ent, const Vector &position); + + // check if origin is visible from the entity side + bool isVisible (const Vector &origin, edict_t *ent); + + // check if entity is alive + bool isAlive (edict_t *ent); + + // check if origin is inside view cone of entity + bool isInViewCone (const Vector &origin, edict_t *ent); + + // checks if entitiy is fakeclient + bool isFakeClient (edict_t *ent); + + // check if entitiy is a player + bool isPlayer (edict_t *ent); + + // check if entity is a vip + bool isPlayerVIP (edict_t *ent); + + // opens config helper + bool openConfig (const char *fileName, const char *errorIfNotExists, MemFile *outFile, bool languageDependant = false); + + // nearest player search helper + bool findNearestPlayer (void **holder, edict_t *to, float searchDistance = 4096.0, bool sameTeam = false, bool needBot = false, bool needAlive = false, bool needDrawn = false, bool needBotWithC4 = false); + + // tracing decals for bots spraying logos + void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex); + + // attaches sound to client struct + void attachSoundsToClients (edict_t *ent, const char *sample, float volume); + + // simulate sound for players + void simulateSoundUpdates (int playerIndex); + + // update stats on clients + void updateClients (); + + // chat helper to strip the clantags out of the string + void stripTags (String &line); + + // chat helper to make player name more human-like + void humanizePlayerName (String &playerName); + + // chat helper to add errors to the bot chat string + void addChatErrors (String &line); + + // chat helper to find keywords for given string + bool checkKeywords (const String &line, String &reply); + + // generates ping bitmask for SVC_PINGS message + int getPingBitmask (edict_t *ent, int loss, int ping); + + // calculate our own pings for all the players + void calculatePings (); + + // send modified pings to all the clients + void sendPings (edict_t *to); + + // installs the sendto function intreception + void installSendTo (); + +public: + + // re-show welcome after changelevel ? + void setNeedForWelcome (bool need) { + m_needToSendWelcome = need; + } + + // get array of clients + SmallArray &getClients () { + return m_clients; + } + + // get clients as const-reference + const SmallArray &getClients () const { + return m_clients; + } + + // get single client as ref + Client &getClient (const int index) { + return m_clients[index]; + } + + // disables send hook + bool disableSendTo () { + return m_sendToHook.disable (); + } + + // enables send hook + bool enableSendTo () { + return m_sendToHook.enable (); + } + +public: + static int32 CR_STDCALL sendTo (int socket, const void *message, size_t length, int flags, const struct sockaddr *dest, int destLength); +}; + +// explose global +static auto &util = BotUtils::get (); \ No newline at end of file diff --git a/include/yapb.h b/include/yapb.h index f6ea476..ff4e80a 100644 --- a/include/yapb.h +++ b/include/yapb.h @@ -332,25 +332,6 @@ CR_DECLARE_SCOPED_ENUM (CollisionProbe, Strafe = cr::bit (2) // probe strafing when colliding ) -// vgui menus (since latest steam updates is obsolete, but left for old cs) -CR_DECLARE_SCOPED_ENUM (GuiMenu, - TeamSelect = 2, // menu select team - TerroristSelect = 26, // terrorist select menu - CTSelect = 27 // ct select menu -) - -// lift usage states -CR_DECLARE_SCOPED_ENUM (LiftState, - None = 0, - LookingButtonOutside, - WaitingFor, - EnteringIn, - WaitingForTeammates, - LookingButtonInside, - TravelingBy, - Leaving -) - // game start messages for counter-strike... CR_DECLARE_SCOPED_ENUM (BotMsg, None = 1, @@ -395,86 +376,8 @@ CR_DECLARE_SCOPED_ENUM (BurstMode, CR_DECLARE_SCOPED_ENUM (Visibility, Head = cr::bit (1), Body = cr::bit (2), - Other = cr::bit (3) -) - -// command handler status -CR_DECLARE_SCOPED_ENUM (BotCommandResult, - Handled = 0, // command successfully handled - ListenServer, // command is only avaialble on listen server - BadFormat // wrong params -) - -// defines for nodes flags field (32 bits are available) -CR_DECLARE_SCOPED_ENUM (NodeFlag, - Lift = cr::bit (1), // wait for lift to be down before approaching this node - Crouch = cr::bit (2), // must crouch to reach this node - Crossing = cr::bit (3), // a target node - Goal = cr::bit (4), // mission goal point (bomb, hostage etc.) - Ladder = cr::bit (5), // node is on ladder - Rescue = cr::bit (6), // node is a hostage rescue point - Camp = cr::bit (7), // node is a camping point - NoHostage = cr::bit (8), // only use this node if no hostage - DoubleJump = cr::bit (9), // bot help's another bot (requster) to get somewhere (using djump) - Sniper = cr::bit (28), // it's a specific sniper point - TerroristOnly = cr::bit (29), // it's a specific terrorist point - CTOnly = cr::bit (30), // it's a specific ct point -) - -// defines for node connection flags field (16 bits are available) -CR_DECLARE_SCOPED_ENUM_TYPE (PathFlag, uint16, - Jump = cr::bit (0) // must jump for this connection -) - -// enum pathfind search type -CR_DECLARE_SCOPED_ENUM (FindPath, - Fast = 0, - Optimal, - Safe -) - -// defines node connection types -CR_DECLARE_SCOPED_ENUM (PathConnection, - Outgoing = 0, - Incoming, - Bidirectional -) - -// defines node add commands -CR_DECLARE_SCOPED_ENUM (GraphAdd, - Normal = 0, -) - -// a* route state -CR_DECLARE_SCOPED_ENUM (RouteState, - Open = 0, - Closed, - New -) - -// node edit states -CR_DECLARE_SCOPED_ENUM (GraphEdit, - On = cr::bit (1), - Noclip = cr::bit (2), - Auto = cr::bit (3) -) - -CR_DECLARE_SCOPED_ENUM (StorageOption, - Practice = cr::bit (0), // this is practice (experience) file - Matrix = cr::bit (1), // this is floyd warshal path & distance matrix - Vistable = cr::bit (2), // this is vistable data - Graph = cr::bit (3), // this is a node graph data - Official = cr::bit (4), // this is additional flag for graph indicates graph are official - Recovered = cr::bit (5), // this is additional flag indicates graph converted from podbot and was bad - Author = cr::bit (6) // this is additional flag indicates that there's author info -) - -CR_DECLARE_SCOPED_ENUM (StorageVersion, - Graph = 1, - Practice = 1, - Vistable = 1, - Matrix = 1, - Podbot = 7 + Other = cr::bit (3), + None = 0 ) // some hardcoded desire defines used to override calculated ones @@ -528,22 +431,8 @@ constexpr int kMaxNodesInsideBucket = kMaxBucketSize / kMaxBucketsInsidePos; constexpr auto kPrimaryWeaponMask = (cr::bit (Weapon::XM1014) | cr::bit (Weapon::M3) | cr::bit (Weapon::MAC10) | cr::bit (Weapon::UMP45) | cr::bit (Weapon::MP5) | cr::bit (Weapon::TMP) | cr::bit (Weapon::P90) | cr::bit (Weapon::AUG) | cr::bit (Weapon::M4A1) | cr::bit (Weapon::SG552) | cr::bit (Weapon::AK47) | cr::bit (Weapon::Scout) | cr::bit (Weapon::SG550) | cr::bit (Weapon::AWP) | cr::bit (Weapon::G3SG1) | cr::bit (Weapon::M249) | cr::bit (Weapon::Famas) | cr::bit (Weapon::Galil)); constexpr auto kSecondaryWeaponMask = (cr::bit (Weapon::P228) | cr::bit (Weapon::Elite) | cr::bit (Weapon::USP) | cr::bit (Weapon::Glock18) | cr::bit (Weapon::Deagle) | cr::bit (Weapon::FiveSeven)); -// a* route -struct Route { - float g, f; - int parent; - RouteState state; -}; - -// general stprage header information structure -struct StorageHeader { - int32 magic; - int32 version; - int32 options; - int32 length; - int32 compressed; - int32 uncompressed; -}; +// include bot graph stuff +#include // links keywords and replies together struct Keywords { @@ -576,29 +465,9 @@ public: BotTask (Task id, float desire, int data, float time, bool resume) : id (id), desire (desire), data (data), time (time), resume (resume) { } }; -// botname structure definition -struct BotName { - String name = ""; - int usedBy = -1; - -public: - BotName () = default; - BotName (String &name, int usedBy) : name (cr::move (name)), usedBy (usedBy) { } -}; - -// voice config structure definition -struct ChatterItem { - String name; - float repeat; - float duration; - -public: - ChatterItem (String name, float repeat, float duration) : name (cr::move (name)), repeat (repeat), duration (duration) { } -}; - // weapon properties structure struct WeaponProp { - char classname[64]; + String classname; int ammo1; // ammo index for primary ammo int ammo1Max; // max primary ammo int slot; // HUD slot (0 based) @@ -618,15 +487,15 @@ struct WeaponInfo { int teamAS; // used by team (as map) int buyGroup; // group in buy menu (standard map) int buySelect; // select item in buy menu (standard map) - int newBuySelectT; // for counter-strike v1.6 - int newBuySelectCT; // for counter-strike v1.6 + int buySelectT; // for counter-strike v1.6 + int buySelectCT; // for counter-strike v1.6 int penetratePower; // penetrate power int maxClip; // max ammo in clip bool primaryFireHold; // hold down primary fire button to use? public: WeaponInfo (int id, const char *name, const char *model, int price, int minPriAmmo, int teamStd, - int teamAs, int buyGroup, int buySelect, int newBuySelectT, int newBuySelectCT, int penetratePower, + int teamAs, int buyGroup, int buySelect, int buySelectT, int buySelectCT, int penetratePower, int maxClip, bool fireHold) { this->id = id; @@ -638,8 +507,8 @@ public: this->teamAS = teamAs; this->buyGroup = buyGroup; this->buySelect = buySelect; - this->newBuySelectCT = newBuySelectCT; - this->newBuySelectT = newBuySelectT; + this->buySelectCT = buySelectCT; + this->buySelectT = buySelectT; this->penetratePower = penetratePower; this->maxClip = maxClip; this->primaryFireHold = fireHold; @@ -664,16 +533,6 @@ struct Client { bool pingUpdate; // update ping ? }; -// bot creation tab -struct CreateQueue { - bool manual; - int difficulty; - int team; - int member; - int personality; - String name; -}; - // define chatting collection structure struct ChatCollection { int chatProbability; @@ -684,87 +543,6 @@ struct ChatCollection { StringArray lastUsedSentences; }; -// general waypoint header information structure -struct PODGraphHeader { - char header[8]; - int32 fileVersion; - int32 pointNumber; - char mapName[32]; - char author[32]; -}; - -// floyd-warshall matrices -struct Matrix { - int16 dist; - int16 index; -}; - -// experience data hold in memory while playing -struct Practice { - int16 damage[kGameTeamNum]; - int16 index[kGameTeamNum]; - int16 value[kGameTeamNum]; -}; - -// defines linked waypoints -struct PathLink { - Vector velocity; - int32 distance; - uint16 flags; - int16 index; -}; - -// defines visibility count -struct PathVis { - uint16 stand, crouch; -}; - -// define graph path structure for yapb -struct Path { - int32 number, flags; - Vector origin, start, end; - float radius, light, display; - PathLink links[kMaxNodeLinks]; - PathVis vis; -}; - -// define waypoint structure for podbot (will convert on load) -struct PODPath { - int32 number, flags; - Vector origin; - float radius, csx, csy, cex, cey; - int16 index[kMaxNodeLinks]; - uint16 conflags[kMaxNodeLinks]; - Vector velocity[kMaxNodeLinks]; - int32 distance[kMaxNodeLinks]; - PathVis vis; -}; - -// this structure links nodes returned from pathfinder -class PathWalk : public IntArray { -public: - explicit PathWalk () { - clear (); - } - - ~PathWalk () = default; -public: - int &next () { - return at (1); - } - - int &first () { - return at (0); - } - - bool hasNext () const { - if (empty ()) { - return false; - } - return length () > 1; - } -}; - // main bot class class Bot final { private: @@ -875,7 +653,7 @@ private: Fight m_fightStyle; // combat style to use CollisionState m_collisionState; // collision State FindPath m_pathType; // which pathfinder to use - uint8 m_visibility; // visibility flags + uint8 m_enemyParts; // visibility flags edict_t *m_pickupItem; // pointer to entity of item to use/pickup edict_t *m_itemIgnore; // pointer to entity to ignore for pickup @@ -937,7 +715,7 @@ private: bool canReplaceWeapon (); bool canDuckUnder (const Vector &normal); bool canJumpUp (const Vector &normal); - bool doneCanJumpUp (const Vector &normal); + bool doneCanJumpUp (const Vector &normal, const Vector &right); bool cantMoveForward (const Vector &normal, TraceResult *tr); bool canStrafeLeft (TraceResult *tr); bool canStrafeRight (TraceResult *tr); @@ -950,7 +728,7 @@ private: bool isWeaponRestricted (int weaponIndex); bool isWeaponRestrictedAMX (int weaponIndex); bool isInViewCone (const Vector &origin); - bool checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart); + bool checkBodyParts (edict_t *target); bool seesEnemy (edict_t *player, bool ignoreFOV = false); bool doPlayerAvoidance (const Vector &normal); bool hasActiveGoal (); @@ -977,6 +755,7 @@ private: bool isEnemyBehindShield (edict_t *enemy); bool checkChatKeywords (String &reply); bool isReplyingToChat (); + bool isReachableNode (int index); void instantChatter (int type); void runAI (); @@ -1267,968 +1046,12 @@ public: } }; -// manager class -class BotManager final : public Singleton { -public: - using ForEachBot = Lambda ; - using UniqueBot = UniquePtr ; - -private: - float m_timeRoundStart; - float m_timeRoundEnd; - float m_timeRoundMid; - - float m_maintainTime; // time to maintain bot creation - float m_quotaMaintainTime; // time to maintain bot quota - float m_grenadeUpdateTime; // time to update active grenades - float m_entityUpdateTime; // time to update intresting entities - float m_plantSearchUpdateTime; // time to update for searching planted bomb - float m_lastChatTime; // global chat time timestamp - float m_timeBombPlanted; // time the bomb were planted - float m_lastRadioTime[kGameTeamNum]; // global radio time - - int m_lastWinner; // the team who won previous round - int m_lastDifficulty; // last bots difficulty - int m_bombSayStatus; // some bot is issued whine about bomb - int m_lastRadio[kGameTeamNum]; // last radio message for team - - bool m_leaderChoosen[kGameTeamNum]; // is team leader choose theese round - bool m_economicsGood[kGameTeamNum]; // is team able to buy anything - bool m_bombPlanted; - bool m_botsCanPause; - bool m_roundEnded; - - Array m_activeGrenades; // holds currently active grenades on the map - Array m_intrestingEntities; // holds currently intresting entities on the map - - SmallArray m_creationTab; // bot creation tab - SmallArray m_filters; // task filters - SmallArray m_bots; // all available bots - - edict_t *m_killerEntity; // killer entity for bots - -protected: - BotCreateResult create (const String &name, int difficulty, int personality, int team, int member); - -public: - BotManager (); - ~BotManager () = default; - -public: - Twin countTeamPlayers (); - - Bot *findBotByIndex (int index); - Bot *findBotByEntity (edict_t *ent); - - Bot *findAliveBot (); - Bot *findHighestFragBot (int team); - - int getHumansCount (bool ignoreSpectators = false); - int getAliveHumansCount (); - int getBotCount (); - float getConnectionTime (int botId); - - void setBombPlanted (bool isPlanted); - void slowFrame (); - void frame (); - void createKillerEntity (); - void destroyKillerEntity (); - void touchKillerEntity (Bot *bot); - void destroy (); - void addbot (const String &name, int difficulty, int personality, int team, int member, bool manual); - void addbot (const String &name, const String &difficulty, const String &personality, const String &team, const String &member, bool manual); - void serverFill (int selection, int personality = Personality::Normal, int difficulty = -1, int numToAdd = -1); - void kickEveryone (bool instant = false, bool zeroQuota = true); - bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned); - void kickBot (int index); - void kickFromTeam (Team team, bool removeAll = false); - void killAllBots (int team = -1); - void maintainQuota (); - void initQuota (); - void initRound (); - void decrementQuota (int by = 1); - void selectLeaders (int team, bool reset); - void listBots (); - void setWeaponMode (int selection); - void updateTeamEconomics (int team, bool setTrue = false); - void updateBotDifficulties (); - void reset (); - void initFilters (); - void resetFilters (); - void updateActiveGrenade (); - void updateIntrestingEntities (); - void captureChatRadio (const char *cmd, const char *arg, edict_t *ent); - void notifyBombDefuse (); - void execGameEntity (entvars_t *vars); - void forEach (ForEachBot handler); - void erase (Bot *bot); - - bool isTeamStacked (int team); - -public: - Array &searchActiveGrenades () { - return m_activeGrenades; - } - - Array &searchIntrestingEntities () { - return m_intrestingEntities; - } - - bool hasActiveGrenades () const { - return !m_activeGrenades.empty (); - } - - bool hasIntrestingEntities () const { - return !m_intrestingEntities.empty (); - } - - bool checkTeamEco (int team) const { - return m_economicsGood[team]; - } - - int getLastWinner () const { - return m_lastWinner; - } - - void setLastWinner (int winner) { - m_lastWinner = winner; - } - - // get the list of filters - SmallArray &getFilters () { - return m_filters; - } - - void createRandom (bool manual = false) { - addbot ("", -1, -1, -1, -1, manual); - } - - bool isBombPlanted () const { - return m_bombPlanted; - } - - float getTimeBombPlanted () const { - return m_timeBombPlanted; - } - - float getRoundStartTime () const { - return m_timeRoundStart; - } - - float getRoundMidTime () const { - return m_timeRoundMid; - } - - float getRoundEndTime () const { - return m_timeRoundEnd; - } - - bool isRoundOver () const { - return m_roundEnded; - } - - void setRoundOver (const bool over) { - m_roundEnded = over; - } - - bool canPause () const { - return m_botsCanPause; - } - - void setCanPause (const bool pause) { - m_botsCanPause = pause; - } - - bool hasBombSay (int type) { - return (m_bombSayStatus & type) == type; - } - - void clearBombSay (int type) { - m_bombSayStatus &= ~type; - } - - void setPlantedBombSearchTimestamp (const float timestamp) { - m_plantSearchUpdateTime = timestamp; - } - - float getPlantedBombSearchTimestamp () const { - return m_plantSearchUpdateTime; - } - - void setLastRadioTimestamp (const int team, const float timestamp) { - if (team == Team::CT || team == Team::Terrorist) { - m_lastRadioTime[team] = timestamp; - } - } - - float getLastRadioTimestamp (const int team) const { - if (team == Team::CT || team == Team::Terrorist) { - return m_lastRadioTime[team]; - } - return 0.0f; - } - - void setLastRadio (const int team, const int radio) { - m_lastRadio[team] = radio; - } - - int getLastRadio (const int team) const { - return m_lastRadio[team]; - } - - void setLastChatTimestamp (const float timestamp) { - m_lastChatTime = timestamp; - } - - float getLastChatTimestamp () const { - return m_lastChatTime; - } - - // some bots are online ? - bool hasBotsOnline () { - return getBotCount () > 0; - } - -public: - Bot *operator [] (int index) { - return findBotByIndex (index); - } - - Bot *operator [] (edict_t *ent) { - return findBotByEntity (ent); - } - -public: - UniqueBot *begin () { - return m_bots.begin (); - } - - UniqueBot *begin () const { - return m_bots.begin (); - } - - UniqueBot *end () { - return m_bots.end (); - } - - UniqueBot *end () const { - return m_bots.end (); - } -}; - -// graph operation class -class BotGraph final : public Singleton { -public: - friend class Bot; - -private: - struct Bucket { - int x, y, z; - }; - - int m_editFlags; - int m_loadAttempts; - int m_cacheNodeIndex; - int m_lastJumpNode; - int m_findWPIndex; - int m_facingAtIndex; - int m_highestDamage[kGameTeamNum]; - - float m_timeJumpStarted; - float m_autoPathDistance; - float m_pathDisplayTime; - float m_arrowDisplayTime; - - bool m_isOnLadder; - bool m_endJumpPoint; - bool m_jumpLearnNode; - bool m_hasChanged; - bool m_needsVisRebuild; - - Vector m_learnVelocity; - Vector m_learnPosition; - Vector m_bombPos; - Vector m_lastNode; - - IntArray m_terrorPoints; - IntArray m_ctPoints; - IntArray m_goalPoints; - IntArray m_campPoints; - IntArray m_sniperPoints; - IntArray m_rescuePoints; - IntArray m_visitedGoals; - - SmallArray m_buckets[kMaxBucketsInsidePos][kMaxBucketsInsidePos][kMaxBucketsInsidePos]; - SmallArray m_matrix; - SmallArray m_practice; - SmallArray m_paths; - SmallArray m_vistable; - - String m_tempStrings; - edict_t *m_editor; - -public: - BotGraph (); - ~BotGraph () = default; - -public: - - int getFacingIndex (); - int getFarest (const Vector &origin, float maxDistance = 32.0); - int getNearest (const Vector &origin, float minDistance = kInfiniteDistance, int flags = -1); - int getNearestNoBuckets (const Vector &origin, float minDistance = kInfiniteDistance, int flags = -1); - int getEditorNeareset (); - int getDangerIndex (int team, int start, int goal); - int getDangerValue (int team, int start, int goal); - int getDangerDamage (int team, int start, int goal); - int getPathDist (int srcIndex, int destIndex); - int clearConnections (int index); - - float calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin); - - bool convertOldFormat (); - bool isVisible (int srcIndex, int destIndex); - bool isStandVisible (int srcIndex, int destIndex); - bool isDuckVisible (int srcIndex, int destIndex); - bool isConnected (int a, int b); - bool isConnected (int index); - bool isReachable (Bot *bot, int index); - bool isNodeReacheable (const Vector &src, const Vector &destination); - bool checkNodes (bool teleportPlayer); - bool loadPathMatrix (); - bool isVisited (int index); - - bool saveGraphData (); - bool loadGraphData (); - - template bool saveStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, const SmallArray &data, uint8 *blob); - template bool loadStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, SmallArray &data, uint8 *blob, int32 *outOptions); - - void saveOldFormat (); - void initGraph (); - void frame (); - void loadPractice (); - void loadVisibility (); - void initNodesTypes (); - void initLightLevels (); - void addPath (int addIndex, int pathIndex, float distance); - void add (int type, const Vector &pos = nullvec); - void erase (int target); - void toggleFlags (int toggleFlag); - void setRadius (int index, float radius); - void rebuildVisibility (); - void pathCreate (char dir); - void erasePath (); - void cachePoint (int index); - void calculatePathRadius (int index); - void savePractice (); - void saveVisibility (); - void addBasic (); - void eraseFromDisk (); - void savePathMatrix (); - void setSearchIndex (int index); - void startLearnJump (); - void setVisited (int index); - void clearVisited (); - void initBuckets (); - void addToBucket (const Vector &pos, int index); - void eraseFromBucket (const Vector &pos, int index); - void setBombPos (bool reset = false, const Vector &pos = nullvec); - void updateGlobalPractice (); - void unassignPath (int from, int to); - void setDangerValue (int team, int start, int goal, int value); - void setDangerDamage (int team, int start, int goal, int value); - void convertFromPOD (Path &path, const PODPath &pod); - void convertToPOD (const Path &path, PODPath &pod); - void convertCampDirection (Path &path); - - const char *getDataDirectory (bool isMemoryFile = false); - const char *getOldFormatGraphName (bool isMemoryFile = false); - - Bucket locateBucket (const Vector &pos); - IntArray searchRadius (float radius, const Vector &origin, int maxCount = -1); - const SmallArray &getNodesInBucket (const Vector &pos); - -public: - int getHighestDamageForTeam (int team) const { - return m_highestDamage[team]; - } - - void setHighestDamageForTeam (int team, int value) { - m_highestDamage[team] = value; - } - - const char *getAuthor () const { - return m_tempStrings.chars (); - } - - bool hasChanged () const { - return m_hasChanged; - } - - bool hasEditFlag (int flag) const { - return !!(m_editFlags & flag); - } - - void setEditFlag (int flag) { - m_editFlags |= flag; - } - - void clearEditFlag (int flag) { - m_editFlags &= ~flag; - } - - void setAutoPathDistance (const float distance) { - m_autoPathDistance = distance; - } - - const Vector &getBombPos () const { - return m_bombPos; - } - - // access paths - Path &operator [] (int index) { - return m_paths[index]; - } - - // check nodes range - bool exists (int index) const { - return index >= 0 && index < static_cast (m_paths.length ()); - } - - // get real nodes num - int length () const { - return m_paths.length (); - } - - // check if has editor - bool hasEditor () const { - return !!m_editor; - } - - // set's the node editor - void setEditor (edict_t *ent) { - m_editor = ent; - } - - // get the current node editor - edict_t *getEditor () { - return m_editor; - } -}; - -// language hasher -struct HashLangString { - uint32 operator () (const String &key) const { - auto str = reinterpret_cast (const_cast (key.chars ())); - uint32 hash = 0; - - while (*str++) { - if (!isalnum (*str)) { - continue; - } - hash = ((*str << 5) + hash) + *str; - } - return hash; - } -}; - -// mostly config stuff, and some stuff dealing with menus -class BotConfig final : public Singleton { -private: - Array m_chat; - Array > m_chatter; - - Array m_botNames; - Array m_replies; - SmallArray m_weapons; - SmallArray m_weaponProps; - - StringArray m_logos; - StringArray m_avatars; - - Dictionary m_language; - - // default tables for personality weapon preferences, overridden by general.cfg - SmallArray m_normalWeaponPrefs = { 0, 2, 1, 4, 5, 6, 3, 12, 10, 24, 25, 13, 11, 8, 7, 22, 23, 18, 21, 17, 19, 15, 17, 9, 14, 16 }; - SmallArray m_rusherWeaponPrefs = { 0, 2, 1, 4, 5, 6, 3, 24, 19, 22, 23, 20, 21, 10, 12, 13, 7, 8, 11, 9, 18, 17, 19, 25, 15, 16 }; - SmallArray m_carefulWeaponPrefs = { 0, 2, 1, 4, 25, 6, 3, 7, 8, 12, 10, 13, 11, 9, 24, 18, 14, 17, 16, 15, 19, 20, 21, 22, 23, 5 }; - SmallArray m_botBuyEconomyTable = { 1900, 2100, 2100, 4000, 6000, 7000, 16000, 1200, 800, 1000, 3000 }; - SmallArray m_grenadeBuyPrecent = { 95, 85, 60 }; - -public: - BotConfig (); - ~BotConfig () = default; - -public: - - // load the configuration files - void loadConfigs (); - - // loads main config file - void loadMainConfig (); - - // loads bot names - void loadNamesConfig (); - - // loads weapons config - void loadWeaponsConfig (); - - // loads chatter config - void loadChatterConfig (); - - // loads chat config - void loadChatConfig (); - - // loads language config - void loadLanguageConfig (); - - // load bots logos config - void loadLogosConfig (); - - // load bots avatars config - void loadAvatarsConfig (); - - // sets memfile to use engine functions - void setupMemoryFiles (); - - // picks random bot name - BotName *pickBotName (); - - // remove bot name from used list - void clearUsedName (Bot *bot); - - // initialize weapon info - void initWeapons (); - - // fix weapon prices (ie for elite) - void adjustWeaponPrices (); - - // find weapon info by weaponi d - WeaponInfo &findWeaponById (int id); - - // translates bot message into needed language - const char *translate (const char *input); - -private: - bool isCommentLine (const String &line) { - const char ch = line.at (0); - return ch == '#' || ch == '/' || ch == '\r' || ch == ';' || ch == 0 || ch == ' '; - }; - -public: - - // checks whether chat banks contains messages - bool hasChatBank (int chatType) const { - return !m_chat[chatType].empty (); - } - - // checks whether chatter banks contains messages - bool hasChatterBank (int chatterType) const { - return !m_chatter[chatterType].empty (); - } - - // pick random phrase from chat bank - const String &pickRandomFromChatBank (int chatType) { - return m_chat[chatType].random (); - } - - // pick random phrase from chatter bank - const ChatterItem &pickRandomFromChatterBank (int chatterType) { - return m_chatter[chatterType].random (); - } - - // gets chatter repeat-interval - float getChatterMessageRepeatInterval (int chatterType) const { - return m_chatter[chatterType][0].repeat; - } - - // get's the replies array - Array &getReplies () { - return m_replies; - } - - // get's the weapon info data - SmallArray &getWeapons () { - return m_weapons; - } - - // get's raw weapon info - WeaponInfo *getRawWeapons () { - return m_weapons.begin (); - } - - // set's the weapon properties - void setWeaponProp (const WeaponProp &prop) { - m_weaponProps[prop.id] = prop; - } - - // get's the weapons prop - const WeaponProp &getWeaponProp (int id) const { - return m_weaponProps[id]; - } - - // get's weapon preferences for personality - int32 *getWeaponPrefs (int personality) const { - switch (personality) { - case Personality::Normal: - default: - return m_normalWeaponPrefs.data (); - - case Personality::Rusher: - return m_rusherWeaponPrefs.data (); - - case Personality::Careful: - return m_carefulWeaponPrefs.data (); - } - } - - // get economics value - int32 *getEconLimit () { - return m_botBuyEconomyTable.data (); - } - - // get's grenade buy percents - bool chanceToBuyGrenade (int grenadeType) const { - return rg.chance (m_grenadeBuyPrecent[grenadeType]); - } - - // get's random avatar for player (if any) - String getRandomAvatar () const { - if (!m_avatars.empty ()) { - return m_avatars.random (); - } - return ""; - } - - // get's random logo index - int getRandomLogoIndex () const { - return m_logos.index (m_logos.random ()); - } - - // get random name by index - const String &getRandomLogoName (int index) const { - return m_logos[index]; - } -}; - -class BotUtils final : public Singleton { -private: - bool m_needToSendWelcome; - float m_welcomeReceiveTime; - - StringArray m_sentences; - SmallArray m_clients; - SmallArray > m_tags; - - SimpleHook m_sendToHook; - -public: - BotUtils (); - ~BotUtils () = default; - -public: - // need to send welcome message ? - void checkWelcome (); - - // gets the weapon alias as hlsting, maybe move to config... - int getWeaponAlias (bool needString, const char *weaponAlias, int weaponIndex = -1); - - // gets the build number of bot - int buildNumber (); - - // gets the shooting cone deviation - float getShootingCone (edict_t *ent, const Vector &position); - - // check if origin is visible from the entity side - bool isVisible (const Vector &origin, edict_t *ent); - - // check if entity is alive - bool isAlive (edict_t *ent); - - // check if origin is inside view cone of entity - bool isInViewCone (const Vector &origin, edict_t *ent); - - // checks if entitiy is fakeclient - bool isFakeClient (edict_t *ent); - - // check if entitiy is a player - bool isPlayer (edict_t *ent); - - // check if entity is a vip - bool isPlayerVIP (edict_t *ent); - - // opens config helper - bool openConfig (const char *fileName, const char *errorIfNotExists, MemFile *outFile, bool languageDependant = false); - - // nearest player search helper - bool findNearestPlayer (void **holder, edict_t *to, float searchDistance = 4096.0, bool sameTeam = false, bool needBot = false, bool needAlive = false, bool needDrawn = false, bool needBotWithC4 = false); - - // tracing decals for bots spraying logos - void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex); - - // attaches sound to client struct - void attachSoundsToClients (edict_t *ent, const char *sample, float volume); - - // simulate sound for players - void simulateSoundUpdates (int playerIndex); - - // update stats on clients - void updateClients (); - - // chat helper to strip the clantags out of the string - void stripTags (String &line); - - // chat helper to make player name more human-like - void humanizePlayerName (String &playerName); - - // chat helper to add errors to the bot chat string - void addChatErrors (String &line); - - // chat helper to find keywords for given string - bool checkKeywords (const String &line, String &reply); - - // generates ping bitmask for SVC_PINGS message - int getPingBitmask (edict_t *ent, int loss, int ping); - - // calculate our own pings for all the players - void calculatePings (); - - // send modified pings to all the clients - void sendPings (edict_t *to); - - // installs the sendto function intreception - void installSendTo (); - -public: - - // re-show welcome after changelevel ? - void setNeedForWelcome (bool need) { - m_needToSendWelcome = need; - } - - // get array of clients - SmallArray &getClients () { - return m_clients; - } - - // get clients as const-reference - const SmallArray &getClients () const { - return m_clients; - } - - // get single client as ref - Client &getClient (const int index) { - return m_clients[index]; - } - - // disables send hook - bool disableSendTo () { - return m_sendToHook.disable (); - } - - // enables send hook - bool enableSendTo () { - return m_sendToHook.enable (); - } - - // checks if string is not empty - bool isEmptyStr (const char *input) const { - if (input == nullptr) { - return true; - } - return *input == '\0'; - } - -public: - static int32 CR_STDCALL sendTo (int socket, const void *message, size_t length, int flags, const struct sockaddr *dest, int destLength); -}; - -// bot command manager -class BotControl final : public Singleton { -public: - using Handler = int (BotControl::*) (); - using MenuHandler = int (BotControl::*) (int); - -public: - // generic bot command - struct BotCmd { - String name, format, help; - Handler handler = nullptr; - - public: - BotCmd () = default; - BotCmd (String name, String format, String help, Handler handler) : name (cr::move (name)), format (cr::move (format)), help (cr::move (help)), handler (cr::move (handler)) { } - }; - - // single bot menu - struct BotMenu { - int ident, slots; - String text; - MenuHandler handler; - - public: - BotMenu (int ident, int slots, String text, MenuHandler handler) : ident (ident), slots (slots), text (cr::move (text)), handler (cr::move (handler)) { } - }; - -private: - StringArray m_args; - Array m_cmds; - Array m_menus; - - edict_t *m_ent; - - bool m_isFromConsole; - bool m_rapidOutput; - bool m_isMenuFillCommand; - int m_menuServerFillTeam; - int m_interMenuData[4] = { 0, }; - -public: - BotControl (); - ~BotControl () = default; - -private: - int cmdAddBot (); - int cmdKickBot (); - int cmdKickBots (); - int cmdKillBots (); - int cmdFill (); - int cmdVote (); - int cmdWeaponMode (); - int cmdVersion (); - int cmdNodeMenu (); - int cmdMenu (); - int cmdList (); - int cmdNode (); - int cmdNodeOn (); - int cmdNodeOff (); - int cmdNodeAdd (); - int cmdNodeAddBasic (); - int cmdNodeSave (); - int cmdNodeLoad (); - int cmdNodeErase (); - int cmdNodeDelete (); - int cmdNodeCheck (); - int cmdNodeCache (); - int cmdNodeClean (); - int cmdNodeSetRadius (); - int cmdNodeSetFlags (); - int cmdNodeTeleport (); - int cmdNodePathCreate (); - int cmdNodePathDelete (); - int cmdNodePathSetAutoDistance (); - int cmdNodeAcquireEditor (); - int cmdNodeReleaseEditor (); - int cmdNodeUpload (); - -private: - int menuMain (int item); - int menuFeatures (int item); - int menuControl (int item); - int menuWeaponMode (int item); - int menuPersonality (int item); - int menuDifficulty (int item); - int menuTeamSelect (int item); - int menuClassSelect (int item); - int menuCommands (int item); - int menuGraphPage1 (int item); - int menuGraphPage2 (int item); - int menuGraphRadius (int item); - int menuGraphType (int item); - int menuGraphFlag (int item); - int menuGraphPath (int item); - int menuAutoPathDistance (int item); - int menuKickPage1 (int item); - int menuKickPage2 (int item); - int menuKickPage3 (int item); - int menuKickPage4 (int item); - -private: - void enableDrawModels (bool enable); - void createMenus (); - -public: - bool executeCommands (); - bool executeMenus (); - - void showMenu (int id); - void kickBotByMenu (int page); - void assignAdminRights (edict_t *ent, char *infobuffer); - void maintainAdminRights (); - -public: - void setFromConsole (bool console) { - m_isFromConsole = console; - } - - void setRapidOutput (bool force) { - m_rapidOutput = force; - } - - void setIssuer (edict_t *ent) { - m_ent = ent; - } - - void fixMissingArgs (size_t num) { - if (num < m_args.length ()) { - return; - } - m_args.resize (num); - } - - int getInt (size_t arg) const { - if (!hasArg (arg)) { - return 0; - } - return m_args[arg].int_ (); - } - - const String &getStr (size_t arg) { - static String empty ("empty"); - - if (!hasArg (arg) || m_args[arg].empty ()) { - return empty; - } - return m_args[arg]; - } - - bool hasArg (size_t arg) const { - return arg < m_args.length (); - } - - void collectArgs () { - m_args.clear (); - - for (int i = 0; i < engfuncs.pfnCmd_Argc (); ++i) { - m_args.emplace (engfuncs.pfnCmd_Argv (i)); - } - } - - // global heloer for sending message to correct channel - template void msg (const char *fmt, Args ...args); - -public: - - // for the server commands - static void handleEngineCommands (); - - // for the client commands - bool handleClientCommands (edict_t *ent); - - // for the client menu commands - bool handleMenuCommands (edict_t *ent); -}; - -// expose bot super-globals -static auto &graph = BotGraph::get (); -static auto &bots = BotManager::get (); -static auto &conf = BotConfig::get (); -static auto &util = BotUtils::get (); -static auto &ctrl = BotControl::get (); - -// include game-related stuff +#include +#include +#include #include +#include +#include // very global convars extern ConVar yb_jasonmode; @@ -2237,30 +1060,4 @@ extern ConVar yb_ignore_enemies; extern ConVar yb_chat; extern ConVar yb_language; extern ConVar yb_show_latency; -extern ConVar yb_enable_query_hook; - -inline int Game::getTeam (edict_t *ent) { - if (game.isNullEntity (ent)) { - return Team::Unassigned; - } - return util.getClient (indexOfPlayer (ent)).team; -} - -// global heloer for sending message to correct channel -template inline void BotControl::msg (const char *fmt, Args ...args) { - auto result = strings.format (fmt, cr::forward (args)...); - - // if no receiver or many message have to appear, just print to server console - if (game.isNullEntity (m_ent) || m_rapidOutput) { - game.print (result); - return; - } - - if (m_isFromConsole || strlen (result) > 48) { - game.clientPrint (m_ent, result); - } - else { - game.centerPrint (m_ent, result); - game.clientPrint (m_ent, result); - } -} +extern ConVar yb_enable_query_hook; \ No newline at end of file diff --git a/project/yapb.vcxproj b/project/yapb.vcxproj index 46c3d3a..f8dc622 100644 --- a/project/yapb.vcxproj +++ b/project/yapb.vcxproj @@ -11,6 +11,8 @@ + + @@ -34,6 +36,10 @@ + + + + @@ -53,6 +59,7 @@ + diff --git a/project/yapb.vcxproj.filters b/project/yapb.vcxproj.filters index 69eb41d..70957ba 100644 --- a/project/yapb.vcxproj.filters +++ b/project/yapb.vcxproj.filters @@ -114,6 +114,24 @@ include\crlib + + include + + + include + + + include + + + include + + + include + + + include + @@ -149,6 +167,9 @@ source + + source + diff --git a/source/Android.mk b/source/Android.mk index 37cd72a..92d3e68 100644 --- a/source/Android.mk +++ b/source/Android.mk @@ -17,6 +17,7 @@ LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) LOCAL_SRC_FILES := \ basecode.cpp \ manager.cpp \ + message.cpp \ chatlib.cpp \ combat.cpp \ control.cpp \ diff --git a/source/basecode.cpp b/source/basecode.cpp index ea92104..e53aa93 100644 --- a/source/basecode.cpp +++ b/source/basecode.cpp @@ -354,10 +354,8 @@ void Bot::avoidGrenades () { float distanceMoved = ((pent->v.origin + pent->v.velocity * getFrameInterval ()) - pev->origin).length (); if (distanceMoved < distance && distance < 500.0f) { - game.makeVectors (pev->v_angle); - - const Vector &dirToPoint = (pev->origin - pent->v.origin).normalize2d (); - const Vector &rightSide = game.vec.right.normalize2d (); + const auto &dirToPoint = (pev->origin - pent->v.origin).normalize2d (); + const auto &rightSide = pev->v_angle.right ().normalize2d (); if ((dirToPoint | rightSide) > 0.0f) { m_needAvoidGrenade = -1; @@ -854,7 +852,7 @@ void Bot::showChaterIcon (bool show) { } auto sendBotVoice = [](bool show, edict_t *ent, int ownId) { - MessageWriter (MSG_ONE, game.getMessageId (NetMsg::BotVoice), nullvec, ent) // begin message + MessageWriter (MSG_ONE, msgs.id (NetMsg::BotVoice), nullvec, ent) // begin message .writeByte (show) // switch on/off .writeByte (ownId); }; @@ -897,7 +895,7 @@ void Bot::instantChatter (int type) { if (!(client.flags & ClientFlags::Used) || (client.ent->v.flags & FL_FAKECLIENT) || client.team != m_team) { continue; } - msg.start (MSG_ONE, game.getMessageId (NetMsg::SendAudio), nullvec, client.ent); // begin message + msg.start (MSG_ONE, msgs.id (NetMsg::SendAudio), nullvec, client.ent); // begin message msg.writeByte (ownIndex); if (pev->deadflag & DEAD_DYING) { @@ -1107,7 +1105,7 @@ void Bot::checkMsgQueue () { bool Bot::isWeaponRestricted (int weaponIndex) { // this function checks for weapon restrictions. - if (util.isEmptyStr (yb_restricted_weapons.str ())) { + if (strings.isEmpty (yb_restricted_weapons.str ())) { return isWeaponRestrictedAMX (weaponIndex); // no banned weapons } auto bannedWeapons = String (yb_restricted_weapons.str ()).split (";"); @@ -1130,7 +1128,7 @@ bool Bot::isWeaponRestrictedAMX (int weaponIndex) { if (cr::bit (weaponIndex) & (kPrimaryWeaponMask | kSecondaryWeaponMask | Weapon::Shield)) { const char *restrictedWeapons = engfuncs.pfnCVarGetString ("amx_restrweapons"); - if (util.isEmptyStr (restrictedWeapons)) { + if (strings.isEmpty (restrictedWeapons)) { return false; } int indices[] = {4, 25, 20, -1, 8, -1, 12, 19, -1, 5, 6, 13, 23, 17, 18, 1, 2, 21, 9, 24, 7, 16, 10, 22, -1, 3, 15, 14, 0, 11}; @@ -1149,7 +1147,7 @@ bool Bot::isWeaponRestrictedAMX (int weaponIndex) { else { const char *restrictedEquipment = engfuncs.pfnCVarGetString ("amx_restrequipammo"); - if (util.isEmptyStr (restrictedEquipment)) { + if (strings.isEmpty (restrictedEquipment)) { return false; } int indices[] = {-1, -1, -1, 3, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 0, 1, 5}; @@ -1176,7 +1174,7 @@ bool Bot::canReplaceWeapon () { return false; } - if (!util.isEmptyStr (yb_restricted_weapons.str ())) { + if (!strings.isEmpty (yb_restricted_weapons.str ())) { auto bannedWeapons = String (yb_restricted_weapons.str ()).split (";"); // check if its banned @@ -1435,10 +1433,10 @@ void Bot::buyStuff () { } else { if (m_team == Team::Terrorist) { - game.botCommand (ent (), "menuselect %d", selectedWeapon->newBuySelectT); + game.botCommand (ent (), "menuselect %d", selectedWeapon->buySelectT); } else { - game.botCommand (ent (), "menuselect %d", selectedWeapon->newBuySelectCT); + game.botCommand (ent (), "menuselect %d", selectedWeapon->buySelectCT); } } } @@ -1529,10 +1527,10 @@ void Bot::buyStuff () { } else { if (m_team == Team::Terrorist) { - game.botCommand (ent (), "menuselect %d", selectedWeapon->newBuySelectT); + game.botCommand (ent (), "menuselect %d", selectedWeapon->buySelectT); } else { - game.botCommand (ent (), "menuselect %d", selectedWeapon->newBuySelectCT); + game.botCommand (ent (), "menuselect %d", selectedWeapon->buySelectCT); } } } @@ -2346,9 +2344,7 @@ void Bot::checkRadioQueue () { getTask ()->time = game.timebase (); m_targetEntity = nullptr; - game.makeVectors (m_radioEntity->v.v_angle); - - m_position = m_radioEntity->v.origin + game.vec.forward * rg.float_ (1024.0f, 2048.0f); + m_position = m_radioEntity->v.origin + m_radioEntity->v.v_angle.forward () * rg.float_ (1024.0f, 2048.0f); clearSearchNodes (); startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); @@ -2403,9 +2399,7 @@ void Bot::checkRadioQueue () { getTask ()->time = game.timebase (); } m_targetEntity = nullptr; - - game.makeVectors (m_radioEntity->v.v_angle); - m_position = m_radioEntity->v.origin + game.vec.forward * rg.float_ (1024.0f, 2048.0f); + m_position = m_radioEntity->v.origin + m_radioEntity->v.v_angle.forward () * rg.float_ (1024.0f, 2048.0f); clearSearchNodes (); startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); @@ -3030,14 +3024,10 @@ void Bot::normal_ () { if (!(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) && !m_reloadState) { m_reloadState = Reload::Primary; } - game.makeVectors (pev->v_angle); - m_timeCamping = game.timebase () + rg.float_ (10.0f, 25.0f); startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, m_timeCamping, true); - game.makeVectors (m_path->start); - - m_camp = m_path->origin + game.vec.forward * 500.0f;; + m_camp = m_path->origin + m_path->start.forward () * 500.0f;; m_aimFlags |= AimFlags::Camp; m_campDirection = 0; @@ -3174,8 +3164,8 @@ void Bot::spraypaint_ () { // bot didn't spray this round? if (m_timeLogoSpray < game.timebase () && getTask ()->time > game.timebase ()) { - game.makeVectors (pev->v_angle); - Vector sprayOrigin = getEyesPos () + game.vec.forward * 128.0f; + const auto &forward = pev->v_angle.forward (); + Vector sprayOrigin = getEyesPos () + forward * 128.0f; TraceResult tr; game.testLine (getEyesPos (), sprayOrigin, TraceIgnore::Monsters, ent (), &tr); @@ -3189,7 +3179,7 @@ void Bot::spraypaint_ () { if (getTask ()->time - 0.5f < game.timebase ()) { // emit spraycan sound engfuncs.pfnEmitSound (ent (), CHAN_VOICE, "player/sprayer.wav", 1.0f, ATTN_NORM, 0, 100); - game.testLine (getEyesPos (), getEyesPos () + game.vec.forward * 128.0f, TraceIgnore::Monsters, ent (), &tr); + game.testLine (getEyesPos (), getEyesPos () + forward * 128.0f, TraceIgnore::Monsters, ent (), &tr); // paint the actual logo decal util.traceDecals (pev, &tr, m_logotypeIndex); @@ -3407,8 +3397,7 @@ void Bot::pause_ () { if (m_moveSpeed < -pev->maxspeed) { m_moveSpeed = -pev->maxspeed; } - game.makeVectors (pev->v_angle); - m_camp = getEyesPos () + game.vec.forward * 500.0f; + m_camp = getEyesPos () + pev->v_angle.forward () * 500.0f; m_aimFlags |= AimFlags::Override; m_wantsToFire = true; @@ -3889,10 +3878,8 @@ void Bot::followUser_ () { } if (m_targetEntity->v.button & IN_ATTACK) { - game.makeVectors (m_targetEntity->v.v_angle); - TraceResult tr; - game.testLine (m_targetEntity->v.origin + m_targetEntity->v.view_ofs, game.vec.forward * 500.0f, TraceIgnore::Everything, ent (), &tr); + game.testLine (m_targetEntity->v.origin + m_targetEntity->v.view_ofs, m_targetEntity->v.v_angle.forward () * 500.0f, TraceIgnore::Everything, ent (), &tr); if (!game.isNullEntity (tr.pHit) && util.isPlayer (tr.pHit) && game.getTeam (tr.pHit) != m_team) { m_targetEntity = nullptr; @@ -4181,10 +4168,8 @@ void Bot::doublejump_ () { else if (inJump && !(m_oldButtons & IN_JUMP)) { pev->button |= IN_JUMP; } - game.makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); - - Vector src = pev->origin + Vector (0.0f, 0.0f, 45.0f); - Vector dest = src + game.vec.up * 256.0f; + const auto &src = pev->origin + Vector (0.0f, 0.0f, 45.0f); + const auto &dest = src + Vector (0.0f, pev->angles.y, 0.0f).upward () * 256.0f; TraceResult tr; game.testLine (src, dest, TraceIgnore::None, ent (), &tr); @@ -4739,7 +4724,7 @@ void Bot::runAI () { } // if bot is trapped under shield yell for help ! - if (getCurrentTaskId () == Task::Camp && hasShield () && isShieldDrawn () && hasFriendNearby >= 2 && seesEnemy (m_enemy)) { + if (getCurrentTaskId () == Task::Camp && hasShield () && isShieldDrawn () && hasFriendNearby >= 2) { pushChatterMessage (Chatter::PinnedDown); } } @@ -5014,16 +4999,12 @@ void Bot::showDebugOverlay () { // blue = ideal angles // red = view angles game.drawLine (game.getLocalEntity (), getEyesPos (), m_destOrigin, 10, 0, Color (0, 255, 0), 250, 5, 1, DrawLine::Arrow); - - game.makeVectors (m_idealAngles); - game.drawLine (game.getLocalEntity (), getEyesPos () - Vector (0.0f, 0.0f, 16.0f), getEyesPos () + game.vec.forward * 300.0f, 10, 0, Color (0, 0, 255), 250, 5, 1, DrawLine::Arrow); - - game.makeVectors (pev->v_angle); - game.drawLine (game.getLocalEntity (), getEyesPos () - Vector (0.0f, 0.0f, 32.0f), getEyesPos () + game.vec.forward * 300.0f, 10, 0, Color (255, 0, 0), 250, 5, 1, DrawLine::Arrow); + game.drawLine (game.getLocalEntity (), getEyesPos () - Vector (0.0f, 0.0f, 16.0f), getEyesPos () + m_idealAngles.forward () * 300.0f, 10, 0, Color (0, 0, 255), 250, 5, 1, DrawLine::Arrow); + game.drawLine (game.getLocalEntity (), getEyesPos () - Vector (0.0f, 0.0f, 32.0f), getEyesPos () + pev->v_angle.forward () * 300.0f, 10, 0, Color (255, 0, 0), 250, 5, 1, DrawLine::Arrow); // now draw line from source to destination - for (size_t i = 0; i < m_pathWalk.length () && i + 1 < m_pathWalk.length (); ++i) { - game.drawLine (game.getLocalEntity (), graph[m_pathWalk[i]].origin, graph[m_pathWalk[i + 1]].origin, 15, 0, Color (255, 100, 55), 200, 5, 1, DrawLine::Arrow); + for (size_t i = m_pathWalk.cursor (); i < m_pathWalk.length () && i + 1 < m_pathWalk.length (); ++i) { + game.drawLine (game.getLocalEntity (), graph[m_pathWalk.at (i)].origin, graph[m_pathWalk.at (i + 1)].origin, 15, 0, Color (255, 100, 55), 200, 5, 1, DrawLine::Arrow); } } } @@ -5109,7 +5090,7 @@ void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { // hurt by unusual damage like drowning or gas else { // leave the camping/hiding position - if (!graph.isReachable (this, graph.getNearest (m_destOrigin))) { + if (!isReachableNode (graph.getNearest (m_destOrigin))) { clearSearchNodes (); findBestNearestNode (); } @@ -5705,7 +5686,7 @@ void Bot::updateHearing () { extern ConVar yb_shoots_thru_walls; // check if heard enemy can be seen - if (checkBodyParts (player, &m_enemyOrigin, &m_visibility)) { + if (checkBodyParts (player)) { m_enemy = player; m_lastEnemy = player; m_lastEnemyOrigin = m_enemyOrigin; diff --git a/source/chatlib.cpp b/source/chatlib.cpp index 605fce6..c6ff8fd 100644 --- a/source/chatlib.cpp +++ b/source/chatlib.cpp @@ -358,7 +358,7 @@ void Bot::checkForChat () { void Bot::say (const char *text) { // this function prints saytext message to all players - if (util.isEmptyStr (text) || !yb_chat.bool_ ()) { + if (strings.isEmpty (text) || !yb_chat.bool_ ()) { return; } game.botCommand (ent (), "say \"%s\"", text); @@ -367,7 +367,7 @@ void Bot::say (const char *text) { void Bot::sayTeam (const char *text) { // this function prints saytext message only for teammates - if (util.isEmptyStr (text) || !yb_chat.bool_ ()) { + if (strings.isEmpty (text) || !yb_chat.bool_ ()) { return; } game.botCommand (ent (), "say_team \"%s\"", text); diff --git a/source/combat.cpp b/source/combat.cpp index 31fbbfc..8b6eccc 100644 --- a/source/combat.cpp +++ b/source/combat.cpp @@ -24,7 +24,7 @@ int Bot::numFriendsNear (const Vector &origin, float radius) { } if ((client.origin - origin).lengthSq () < cr::square (radius)) { - ++count; + count++; } } return count; @@ -39,7 +39,7 @@ int Bot::numEnemiesNear (const Vector &origin, float radius) { } if ((client.origin - origin).lengthSq () < cr::square (radius)) { - ++count; + count++; } } return count; @@ -87,39 +87,39 @@ bool Bot::isEnemyHidden (edict_t *enemy) { return false; } -bool Bot::checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart) { +bool Bot::checkBodyParts (edict_t *target) { // this function checks visibility of a bot target. if (isEnemyHidden (target)) { - *bodyPart = 0; - origin->clear (); + m_enemyParts = Visibility::None; + m_enemyOrigin = nullvec; return false; } TraceResult result; + auto eyes = getEyesPos (); - Vector eyes = getEyesPos (); - Vector spot = target->v.origin; + auto spot = target->v.origin; + auto self = pev->pContainingEntity; - *bodyPart = 0; - - game.testLine (eyes, spot, TraceIgnore::Everything, ent (), &result); + m_enemyParts = Visibility::None; + game.testLine (eyes, spot, TraceIgnore::Everything, self, &result); if (result.flFraction >= 1.0f) { - *bodyPart |= Visibility::Body; - *origin = result.vecEndPos; + m_enemyParts |= Visibility::Body; + m_enemyOrigin = result.vecEndPos; } // check top of head spot.z += 25.0f; - game.testLine (eyes, spot, TraceIgnore::Everything, ent (), &result); + game.testLine (eyes, spot, TraceIgnore::Everything, self, &result); if (result.flFraction >= 1.0f) { - *bodyPart |= Visibility::Head; - *origin = result.vecEndPos; + m_enemyParts |= Visibility::Head; + m_enemyOrigin = result.vecEndPos; } - if (*bodyPart != 0) { + if (m_enemyParts != 0) { return true; } @@ -132,11 +132,11 @@ bool Bot::checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart) { else { spot.z = target->v.origin.z - standFeet; } - game.testLine (eyes, spot, TraceIgnore::Everything, ent (), &result); + game.testLine (eyes, spot, TraceIgnore::Everything, self, &result); if (result.flFraction >= 1.0f) { - *bodyPart |= Visibility::Other; - *origin = result.vecEndPos; + m_enemyParts |= Visibility::Other; + m_enemyOrigin = result.vecEndPos; return true; } @@ -147,21 +147,21 @@ bool Bot::checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart) { Vector perp (-dir.y, dir.x, 0.0f); spot = target->v.origin + Vector (perp.x * edgeOffset, perp.y * edgeOffset, 0); - game.testLine (eyes, spot, TraceIgnore::Everything, ent (), &result); + game.testLine (eyes, spot, TraceIgnore::Everything, self, &result); if (result.flFraction >= 1.0f) { - *bodyPart |= Visibility::Other; - *origin = result.vecEndPos; + m_enemyParts |= Visibility::Other; + m_enemyOrigin = result.vecEndPos; return true; } spot = target->v.origin - Vector (perp.x * edgeOffset, perp.y * edgeOffset, 0); - game.testLine (eyes, spot, TraceIgnore::Everything, ent (), &result); + game.testLine (eyes, spot, TraceIgnore::Everything, self, &result); if (result.flFraction >= 1.0f) { - *bodyPart |= Visibility::Other; - *origin = result.vecEndPos; + m_enemyParts |= Visibility::Other; + m_enemyOrigin = result.vecEndPos; return true; } @@ -177,7 +177,7 @@ bool Bot::seesEnemy (edict_t *player, bool ignoreFOV) { ignoreFOV = true; } - if ((ignoreFOV || isInViewCone (player->v.origin)) && checkBodyParts (player, &m_enemyOrigin, &m_visibility)) { + if ((ignoreFOV || isInViewCone (player->v.origin)) && checkBodyParts (player)) { m_seeEnemyTime = game.timebase (); m_lastEnemy = player; m_lastEnemyOrigin = m_enemyOrigin; @@ -207,12 +207,9 @@ bool Bot::lookupEnemies () { m_states |= Sense::SuspectEnemy; m_aimFlags |= AimFlags::LastEnemy; } - m_visibility = 0; + m_enemyParts = Visibility::None; m_enemyOrigin= nullvec; - // setup potential visibility set from engine - auto set = game.getVisibilitySet (this, true); - if (!game.isNullEntity (m_enemy)) { player = m_enemy; @@ -224,13 +221,14 @@ bool Bot::lookupEnemies () { // the old enemy is no longer visible or if (game.isNullEntity (newEnemy)) { + auto set = game.getVisibilitySet (this, true); // setup potential visibility set from engine // ignore shielded enemies, while we have real one edict_t *shieldEnemy = nullptr; // search the world for players... for (const auto &client : util.getClients ()) { - if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team == m_team || client.ent == ent ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team == m_team || client.ent == ent () || !client.ent) { continue; } player = client.ent; @@ -264,7 +262,7 @@ bool Bot::lookupEnemies () { } } } - m_enemyUpdateTime = game.timebase () + getFrameInterval () * 30.0f; + m_enemyUpdateTime = cr::clamp (game.timebase () + getFrameInterval () * 25.0f, 0.5f, 0.75f); if (game.isNullEntity (newEnemy) && !game.isNullEntity (shieldEnemy)) { newEnemy = shieldEnemy; @@ -422,38 +420,38 @@ const Vector &Bot::getEnemyBodyOffset () { }; // if no visibility data, use last one - if (!m_visibility) { + if (!m_enemyParts) { return m_enemyOrigin; } float distance = (m_enemy->v.origin - pev->origin).length (); // do not aim at head, at long distance (only if not using sniper weapon) - if ((m_visibility & Visibility::Body) && !usesSniper () && distance > (m_difficulty > 2 ? 2000.0f : 1000.0f)) { - m_visibility &= ~Visibility::Head; + if ((m_enemyParts & Visibility::Body) && !usesSniper () && distance > (m_difficulty > 2 ? 2000.0f : 1000.0f)) { + m_enemyParts &= ~Visibility::Head; } // do not aim at head while close enough to enemy and having sniper else if (distance < 800.0f && usesSniper ()) { - m_visibility &= ~Visibility::Head; + m_enemyParts &= ~Visibility::Head; } // do not aim at head while enemy is soooo close enough to enemy when recoil aims at head automatically else if (distance < kSprayDistance) { - m_visibility &= ~Visibility::Head; + m_enemyParts &= ~Visibility::Head; } Vector aimPos = m_enemy->v.origin; - if (m_difficulty > 2 && !(m_visibility & Visibility::Other)) { + if (m_difficulty > 2 && !(m_enemyParts & Visibility::Other)) { aimPos = (m_enemy->v.velocity - pev->velocity) * getFrameInterval () + aimPos; } // if we only suspect an enemy behind a wall take the worst skill - if (!m_visibility && (m_states & Sense::SuspectEnemy)) { + if (!m_enemyParts && (m_states & Sense::SuspectEnemy)) { aimPos += getBodyOffsetError (distance); } else { // now take in account different parts of enemy body - if (m_visibility & (Visibility::Head | Visibility::Body)) { + if (m_enemyParts & (Visibility::Head | Visibility::Body)) { int headshotFreq[5] = { 20, 40, 60, 80, 100 }; // now check is our skill match to aim at head, else aim at enemy body @@ -464,13 +462,13 @@ const Vector &Bot::getEnemyBodyOffset () { aimPos.z += getEnemyBodyOffsetCorrection (distance); } } - else if (m_visibility & Visibility::Body) { + else if (m_enemyParts & Visibility::Body) { aimPos.z += getEnemyBodyOffsetCorrection (distance); } - else if (m_visibility & Visibility::Other) { + else if (m_enemyParts & Visibility::Other) { aimPos = m_enemyOrigin; } - else if (m_visibility & Visibility::Head) { + else if (m_enemyParts & Visibility::Head) { aimPos.z = headOffset (m_enemy) + getEnemyBodyOffsetCorrection (distance); } } @@ -531,17 +529,16 @@ bool Bot::isFriendInLineOfFire (float distance) { if (!mp_friendlyfire.bool_ () || game.is (GameFlags::CSDM)) { return false; } - game.makeVectors (pev->v_angle); TraceResult tr; - game.testLine (getEyesPos (), getEyesPos () + distance * pev->v_angle, TraceIgnore::None, ent (), &tr); + game.testLine (getEyesPos (), getEyesPos () + distance * pev->v_angle.normalize (), TraceIgnore::None, ent (), &tr); // check if we hit something if (util.isPlayer (tr.pHit) && tr.pHit != ent ()) { auto hit = tr.pHit; // check valid range - if (util.isAlive (hit) && game.getTeam (hit) == m_team) { + if (game.getTeam (hit) == m_team && util.isAlive (hit)) { return true; } } @@ -551,12 +548,9 @@ bool Bot::isFriendInLineOfFire (float distance) { if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team != m_team || client.ent == ent ()) { continue; } - edict_t *pent = client.ent; + auto friendDistance = (client.ent->v.origin - pev->origin).lengthSq (); - float friendDistance = (pent->v.origin - pev->origin).length (); - float squareDistance = cr::sqrtf (1089.0f + cr::square (friendDistance)); - - if (friendDistance <= distance && util.getShootingCone (ent (), pent->v.origin) > cr::square (friendDistance) / cr::square (squareDistance)) { + if (friendDistance <= distance && util.getShootingCone (ent (), client.ent->v.origin) > friendDistance / (friendDistance + 1089.0f)) { return true; } } @@ -634,14 +628,14 @@ bool Bot::isPenetrableObstacle2 (const Vector &dest) { game.testLine (source, dest, TraceIgnore::Everything, ent (), &tr); while (tr.flFraction != 1.0f && numHits < 3) { - ++numHits; - ++thikness; + numHits++; + thikness++; point = tr.vecEndPos + direction; while (engfuncs.pfnPointContents (point) == CONTENTS_SOLID && thikness < 98) { point = point + direction; - ++thikness; + thikness++; } game.testLine (point, dest, TraceIgnore::Everything, ent (), &tr); } @@ -712,7 +706,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { // select this weapon if it isn't already selected if (m_currentWeapon != id) { const auto &prop = conf.getWeaponProp (id); - selectWeaponByName (prop.classname); + selectWeaponByName (prop.classname.chars ()); // reset burst fire variables m_firePause = 0.0f; @@ -729,7 +723,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { if (tab[choosen].id == id) { break; } - ++choosen; + choosen++; } } @@ -741,7 +735,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { if (distance >= 750.0f && !isShieldDrawn ()) { pev->button |= IN_ATTACK2; // draw the shield } - else if (isShieldDrawn () || (!game.isNullEntity (m_enemy) && ((m_enemy->v.button & IN_RELOAD) || !seesEnemy (m_enemy)))) { + else if (isShieldDrawn () || (!game.isNullEntity (m_enemy) && ((m_enemy->v.button & IN_RELOAD) || !seesEntity (m_enemy->v.origin)))) { pev->button |= IN_ATTACK2; // draw out the shield } m_shieldCheckTime = game.timebase () + 1.0f; @@ -893,7 +887,7 @@ void Bot::fireWeapons () { choosenWeapon = selectIndex; } } - ++selectIndex; + selectIndex++; } selectId = tab[choosenWeapon].id; @@ -923,7 +917,7 @@ void Bot::fireWeapons () { return; } } - ++selectIndex; + selectIndex++; } selectId = Weapon::Knife; // no available ammo, use knife! } @@ -1052,7 +1046,7 @@ void Bot::attackMovement () { m_moveSpeed = -pev->maxspeed; } - if (usesSniper () || !(m_visibility & (Visibility::Body | Visibility::Head))) { + if (usesSniper () || !(m_enemyParts & (Visibility::Body | Visibility::Head))) { m_fightStyle = Fight::Stay; m_lastFightStyleCheck = game.timebase (); } @@ -1098,10 +1092,8 @@ void Bot::attackMovement () { if (m_strafeSetTime < game.timebase ()) { // to start strafing, we have to first figure out if the target is on the left side or right side - game.makeVectors (m_enemy->v.v_angle); - - const Vector &dirToPoint = (pev->origin - m_enemy->v.origin).normalize2d (); - const Vector &rightSide = game.vec.right.normalize2d (); + const auto &dirToPoint = (pev->origin - m_enemy->v.origin).normalize2d (); + const auto &rightSide = m_enemy->v.v_angle.right ().normalize2d (); if ((dirToPoint | rightSide) < 0) { m_combatStrafeDir = Dodge::Left; @@ -1148,7 +1140,7 @@ void Bot::attackMovement () { } } else if (m_fightStyle == Fight::Stay) { - if ((m_visibility & (Visibility::Head | Visibility::Body)) && !(m_visibility & Visibility::Other) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) { + if ((m_enemyParts & (Visibility::Head | Visibility::Body)) && !(m_enemyParts & Visibility::Other) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) { int enemyNearestIndex = graph.getNearest (m_enemy->v.origin); if (graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) { @@ -1176,9 +1168,10 @@ void Bot::attackMovement () { } if (!isInWater () && !isOnLadder () && (m_moveSpeed > 0.0f || m_strafeSpeed >= 0.0f)) { - game.makeVectors (pev->v_angle); + Vector right, forward; + pev->v_angle.buildVectors (&forward, &right, nullptr); - if (isDeadlyMove (pev->origin + (game.vec.forward * m_moveSpeed * 0.2f) + (game.vec.right * m_strafeSpeed * 0.2f) + (pev->velocity * getFrameInterval ()))) { + if (isDeadlyMove (pev->origin + (forward * m_moveSpeed * 0.2f) + (right * m_strafeSpeed * 0.2f) + (pev->velocity * getFrameInterval ()))) { m_strafeSpeed = -m_strafeSpeed; m_moveSpeed = -m_moveSpeed; @@ -1245,8 +1238,8 @@ bool Bot::usesRifle () { if (m_currentWeapon == tab->id) { break; } - ++tab; - ++count; + tab++; + count++; } if (tab->id && count > 13) { @@ -1264,8 +1257,8 @@ bool Bot::usesPistol () { if (m_currentWeapon == tab->id) { break; } - ++tab; - ++count; + tab++; + count++; } if (tab->id && count < 7) { @@ -1309,7 +1302,7 @@ int Bot::bestPrimaryCarried () { if (weapons & cr::bit (weaponTab[*pref].id)) { weaponIndex = i; } - ++pref; + pref++; } return weaponIndex; } @@ -1335,7 +1328,7 @@ int Bot::bestSecondaryCarried () { weaponIndex = i; break; } - ++pref; + pref++; } return weaponIndex; } @@ -1366,7 +1359,7 @@ bool Bot::rateGroundWeapon (edict_t *ent) { groundIndex = i; break; } - ++pref; + pref++; } int hasWeapon = 0; @@ -1405,7 +1398,7 @@ void Bot::selectBestWeapon () { while (tab[selectIndex].id) { // is the bot NOT carrying this weapon? if (!(pev->weapons & cr::bit (tab[selectIndex].id))) { - ++selectIndex; // skip to next weapon + selectIndex++; // skip to next weapon continue; } @@ -1426,7 +1419,7 @@ void Bot::selectBestWeapon () { if (ammoLeft) { chosenWeaponIndex = selectIndex; } - ++selectIndex; + selectIndex++; } chosenWeaponIndex %= kNumWeapons + 1; @@ -1465,7 +1458,7 @@ int Bot::bestWeaponCarried () { num = i; } ++i; - ++tab; + tab++; } return num; } @@ -1589,7 +1582,7 @@ void Bot::checkReload () { } if (weapons == 0) { - ++m_reloadState; + m_reloadState++; if (m_reloadState > Reload::Secondary) { m_reloadState = Reload::None; @@ -1607,7 +1600,7 @@ void Bot::checkReload () { if (m_ammoInClip[weaponIndex] < conf.findWeaponById (weaponIndex).maxClip * 0.8f && prop.ammo1 != -1 && prop.ammo1 < 32 && m_ammo[prop.ammo1] > 0) { if (m_currentWeapon != weaponIndex) { - selectWeaponByName (prop.classname); + selectWeaponByName (prop.classname.chars ()); } pev->button &= ~IN_ATTACK; @@ -1622,7 +1615,7 @@ void Bot::checkReload () { m_reloadState = Reload::None; return; } - ++m_reloadState; + m_reloadState++; if (m_reloadState > Reload::Secondary) { m_reloadState = Reload::None; diff --git a/source/control.cpp b/source/control.cpp index 7a303ba..3ecc07b 100644 --- a/source/control.cpp +++ b/source/control.cpp @@ -1641,7 +1641,7 @@ void BotControl::showMenu (int id) { Client &client = util.getClient (game.indexOfPlayer (m_ent)); if (id == Menu::None) { - MessageWriter (MSG_ONE_UNRELIABLE, game.getMessageId (NetMsg::ShowMenu), nullvec, m_ent) + MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullvec, m_ent) .writeShort (0) .writeChar (0) .writeByte (0) @@ -1657,7 +1657,7 @@ void BotControl::showMenu (int id) { MessageWriter msg; while (strlen (text) >= 64) { - msg.start (MSG_ONE_UNRELIABLE, game.getMessageId (NetMsg::ShowMenu), nullvec, m_ent) + msg.start (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullvec, m_ent) .writeShort (display.slots) .writeChar (-1) .writeByte (1); @@ -1669,7 +1669,7 @@ void BotControl::showMenu (int id) { text += 64; } - MessageWriter (MSG_ONE_UNRELIABLE, game.getMessageId (NetMsg::ShowMenu), nullvec, m_ent) + MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullvec, m_ent) .writeShort (display.slots) .writeChar (-1) .writeByte (0) @@ -1753,7 +1753,7 @@ void BotControl::maintainAdminRights () { Client &client = util.getClient (i); if (client.flags & ClientFlags::Admin) { - if (util.isEmptyStr (yb_password_key.str ()) && util.isEmptyStr (yb_password.str ())) { + if (strings.isEmpty (yb_password_key.str ()) && strings.isEmpty (yb_password.str ())) { client.flags &= ~ClientFlags::Admin; } else if (!!strcmp (yb_password.str (), engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (client.ent), const_cast (yb_password_key.str ())))) { @@ -1761,7 +1761,7 @@ void BotControl::maintainAdminRights () { game.print ("Player %s had lost remote access to %s.", STRING (player->v.netname), PRODUCT_SHORT_NAME); } } - else if (!(client.flags & ClientFlags::Admin) && !util.isEmptyStr (yb_password_key.str ()) && !util.isEmptyStr (yb_password.str ())) { + else if (!(client.flags & ClientFlags::Admin) && !strings.isEmpty (yb_password_key.str ()) && !strings.isEmpty (yb_password.str ())) { if (strcmp (yb_password.str (), engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (client.ent), const_cast (yb_password_key.str ()))) == 0) { client.flags |= ClientFlags::Admin; game.print ("Player %s had gained full remote access to %s.", STRING (player->v.netname), PRODUCT_SHORT_NAME); diff --git a/source/engine.cpp b/source/engine.cpp index 64cc861..17859ac 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -17,11 +17,6 @@ Game::Game () { m_startEntity = nullptr; m_localEntity = nullptr; - resetMessages (); - - for (auto &msg : m_msgBlock.regMsgs) { - msg = NetMsg::None; - } m_precached = false; m_isBotCommand = false; @@ -35,10 +30,6 @@ Game::Game () { m_cvars.clear (); } -Game::~Game () { - resetMessages (); -} - void Game::precache () { if (m_precached) { return; @@ -387,7 +378,7 @@ uint8 *Game::getVisibilitySet (Bot *bot, bool pvs) { void Game::sendClientMessage (bool console, edict_t *ent, const char *message) { // helper to sending the client message - MessageWriter (MSG_ONE, getMessageId (NetMsg::TextMsg), nullvec, ent) + MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullvec, ent) .writeByte (console ? HUD_PRINTCONSOLE : HUD_PRINTCENTER) .writeString (message); } @@ -541,490 +532,6 @@ void Game::registerCvars (bool gameVars) { } } -void Game::processMessages (void *ptr) { - if (m_msgBlock.msg == NetMsg::None) { - return; - } - - // some needed variables - static uint8 r, g, b; - static uint8 enabled; - - static int damageArmor, damageTaken, damageBits; - static int killerIndex, victimIndex, playerIndex; - static int index, numPlayers; - static int state, id, clip; - - static Vector damageOrigin; - static WeaponProp weaponProp; - - // some widely used stuff - auto bot = bots[m_msgBlock.bot]; - - auto strVal = reinterpret_cast (ptr); - auto intVal = *reinterpret_cast (ptr); - auto byteVal = *reinterpret_cast (ptr); - - // now starts of network message execution - switch (m_msgBlock.msg) { - case NetMsg::VGUI: - // this message is sent when a VGUI menu is displayed. - - if (bot != nullptr && m_msgBlock.state == 0) { - switch (intVal) { - case GuiMenu::TeamSelect: - bot->m_startAction = BotMsg::TeamSelect; - break; - - case GuiMenu::TerroristSelect: - case GuiMenu::CTSelect: - bot->m_startAction = BotMsg::ClassSelect; - break; - } - } - break; - - case NetMsg::ShowMenu: - // this message is sent when a text menu is displayed. - - // ignore first 3 fields of message - if (m_msgBlock.state < 3 || bot == nullptr) { - break; - } - - if (strcmp (strVal, "#Team_Select") == 0) { - bot->m_startAction = BotMsg::TeamSelect; - } - else if (strcmp (strVal, "#Team_Select_Spect") == 0) { - bot->m_startAction = BotMsg::TeamSelect; - } - else if (strcmp (strVal, "#IG_Team_Select_Spect") == 0) { - bot->m_startAction = BotMsg::TeamSelect; - } - else if (strcmp (strVal, "#IG_Team_Select") == 0) { - bot->m_startAction = BotMsg::TeamSelect; - } - else if (strcmp (strVal, "#IG_VIP_Team_Select") == 0) { - bot->m_startAction = BotMsg::TeamSelect; - } - else if (strcmp (strVal, "#IG_VIP_Team_Select_Spect") == 0) { - bot->m_startAction = BotMsg::TeamSelect; - } - else if (strcmp (strVal, "#Terrorist_Select") == 0) { - bot->m_startAction = BotMsg::ClassSelect; - } - else if (strcmp (strVal, "#CT_Select") == 0) { - bot->m_startAction = BotMsg::ClassSelect; - } - break; - - case NetMsg::WeaponList: - // this message is sent when a client joins the game. All of the weapons are sent with the weapon ID and information about what ammo is used. - - switch (m_msgBlock.state) { - case 0: - strncpy (weaponProp.classname, strVal, cr::bufsize (weaponProp.classname)); - break; - - case 1: - weaponProp.ammo1 = intVal; // ammo index 1 - break; - - case 2: - weaponProp.ammo1Max = intVal; // max ammo 1 - break; - - case 5: - weaponProp.slot = intVal; // slot for this weapon - break; - - case 6: - weaponProp.pos = intVal; // position in slot - break; - - case 7: - weaponProp.id = intVal; // weapon ID - break; - - case 8: - weaponProp.flags = intVal; // flags for weapon (WTF???) - conf.setWeaponProp (weaponProp); // store away this weapon with it's ammo information... - break; - } - break; - - case NetMsg::CurWeapon: - // this message is sent when a weapon is selected (either by the bot chosing a weapon or by the server auto assigning the bot a weapon). In CS it's also called when Ammo is increased/decreased - - switch (m_msgBlock.state) { - case 0: - state = intVal; // state of the current weapon (WTF???) - break; - - case 1: - id = intVal; // weapon ID of current weapon - break; - - case 2: - clip = intVal; // ammo currently in the clip for this weapon - - if (bot != nullptr && id <= 31) { - if (state != 0) { - bot->m_currentWeapon = id; - } - - // ammo amount decreased ? must have fired a bullet... - if (id == bot->m_currentWeapon && bot->m_ammoInClip[id] > clip) { - bot->m_timeLastFired = timebase (); // remember the last bullet time - } - bot->m_ammoInClip[id] = clip; - } - break; - } - break; - - case NetMsg::AmmoX: - // this message is sent whenever ammo amounts are adjusted (up or down). NOTE: Logging reveals that CS uses it very unreliable! - - switch (m_msgBlock.state) { - case 0: - index = intVal; // ammo index (for type of ammo) - break; - - case 1: - if (bot != nullptr) { - bot->m_ammo[index] = intVal; // store it away - } - break; - } - break; - - case NetMsg::AmmoPickup: - // this message is sent when the bot picks up some ammo (AmmoX messages are also sent so this message is probably - // not really necessary except it allows the HUD to draw pictures of ammo that have been picked up. The bots - // don't really need pictures since they don't have any eyes anyway. - - switch (m_msgBlock.state) { - case 0: - index = intVal; - break; - - case 1: - if (bot != nullptr) { - bot->m_ammo[index] = intVal; - } - break; - } - break; - - case NetMsg::Damage: - // this message gets sent when the bots are getting damaged. - - switch (m_msgBlock.state) { - case 0: - damageArmor = intVal; - break; - - case 1: - damageTaken = intVal; - break; - - case 2: - damageBits = intVal; - - if (bot != nullptr && (damageArmor > 0 || damageTaken > 0)) { - bot->processDamage (bot->pev->dmg_inflictor, damageTaken, damageArmor, damageBits); - } - break; - } - break; - - case NetMsg::Money: - // this message gets sent when the bots money amount changes - - if (bot != nullptr && m_msgBlock.state == 0) { - bot->m_moneyAmount = intVal; // amount of money - } - break; - - case NetMsg::StatusIcon: - switch (m_msgBlock.state) { - case 0: - enabled = byteVal; - break; - - case 1: - if (bot != nullptr) { - if (strcmp (strVal, "buyzone") == 0) { - bot->m_inBuyZone = (enabled != 0); - - // try to equip in buyzone - bot->processBuyzoneEntering (BuyState::PrimaryWeapon); - } - else if (strcmp (strVal, "vipsafety") == 0) { - bot->m_inVIPZone = (enabled != 0); - } - else if (strcmp (strVal, "c4") == 0) { - bot->m_inBombZone = (enabled == 2); - } - } - break; - } - break; - - case NetMsg::DeathMsg: // this message sends on death - switch (m_msgBlock.state) { - case 0: - killerIndex = intVal; - break; - - case 1: - victimIndex = intVal; - break; - - case 2: - if (killerIndex != 0 && killerIndex != victimIndex) { - edict_t *killer = entityOfIndex (killerIndex); - edict_t *victim = entityOfIndex (victimIndex); - - if (isNullEntity (killer) || isNullEntity (victim)) { - break; - } - - if (yb_radio_mode.int_ () == 2) { - // need to send congrats on well placed shot - for (const auto ¬ify : bots) { - if (notify->m_notKilled && killer != notify->ent () && notify->seesEntity (victim->v.origin) && getTeam (killer) == notify->m_team && getTeam (killer) != getTeam (victim)) { - if (!bots[killer]) { - notify->processChatterMessage ("#Bot_NiceShotCommander"); - } - else { - notify->processChatterMessage ("#Bot_NiceShotPall"); - } - break; - } - } - } - - // notice nearby to victim teammates, that attacker is near - for (const auto ¬ify : bots) { - if (notify->m_seeEnemyTime + 2.0f < timebase () && notify->m_notKilled && notify->m_team == getTeam (victim) && util.isVisible (killer->v.origin, notify->ent ()) && isNullEntity (notify->m_enemy) && getTeam (killer) != getTeam (victim)) { - notify->m_actualReactionTime = 0.0f; - notify->m_seeEnemyTime = timebase (); - notify->m_enemy = killer; - notify->m_lastEnemy = killer; - notify->m_lastEnemyOrigin = killer->v.origin; - } - } - - auto notify = bots[killer]; - - // is this message about a bot who killed somebody? - if (notify != nullptr) { - notify->m_lastVictim = victim; - } - else // did a human kill a bot on his team? - { - auto target = bots[victim]; - - if (target != nullptr) { - if (getTeam (killer) == target->m_team) { - target->m_voteKickIndex = killerIndex; - } - target->m_notKilled = false; - } - } - } - break; - } - break; - - case NetMsg::ScreenFade: // this message gets sent when the screen fades (flashbang) - switch (m_msgBlock.state) { - case 3: - r = byteVal; - break; - - case 4: - g = byteVal; - break; - - case 5: - b = byteVal; - break; - - case 6: - if (bot != nullptr && r >= 255 && g >= 255 && b >= 255 && byteVal > 170) { - bot->processBlind (byteVal); - } - break; - } - break; - - case NetMsg::HLTV: // round restart in steam cs - switch (m_msgBlock.state) { - case 0: - numPlayers = intVal; - break; - - case 1: - if (numPlayers == 0 && intVal == 0) { - bots.initRound (); - } - break; - } - break; - - case NetMsg::TextMsg: - if (m_msgBlock.state == 1) { - if (strcmp (strVal, "#CTs_Win") == 0 || - strcmp (strVal, "#Bomb_Defused") == 0 || - strcmp (strVal, "#Terrorists_Win") == 0 || - strcmp (strVal, "#Round_Draw") == 0 || - strcmp (strVal, "#All_Hostages_Rescued") == 0 || - strcmp (strVal, "#Target_Saved") == 0 || - strcmp (strVal, "#Hostages_Not_Rescued") == 0 || - strcmp (strVal, "#Terrorists_Not_Escaped") == 0 || - strcmp (strVal, "#VIP_Not_Escaped") == 0 || - strcmp (strVal, "#Escaping_Terrorists_Neutralized") == 0 || - strcmp (strVal, "#VIP_Assassinated") == 0 || - strcmp (strVal, "#VIP_Escaped") == 0 || - strcmp (strVal, "#Terrorists_Escaped") == 0 || - strcmp (strVal, "#CTs_PreventEscape") == 0 || - strcmp (strVal, "#Target_Bombed") == 0 || - strcmp (strVal, "#Game_Commencing") == 0 || - strcmp (strVal, "#Game_will_restart_in") == 0) { - bots.setRoundOver (true); - - if (strcmp (strVal, "#Game_Commencing") == 0) { - util.setNeedForWelcome (true); - } - - if (strcmp (strVal, "#CTs_Win") == 0) { - bots.setLastWinner (Team::CT); // update last winner for economics - - if (yb_radio_mode.int_ () == 2) { - Bot *notify = bots.findAliveBot (); - - if (notify != nullptr && notify->m_notKilled) { - notify->processChatterMessage (strVal); - } - } - } - - if (strcmp (strVal, "#Game_will_restart_in") == 0) { - bots.updateTeamEconomics (Team::CT, true); - bots.updateTeamEconomics (Team::Terrorist, true); - } - - if (strcmp (strVal, "#Terrorists_Win") == 0) { - bots.setLastWinner (Team::Terrorist); // update last winner for economics - - if (yb_radio_mode.int_ () == 2) { - Bot *notify = bots.findAliveBot (); - - if (notify != nullptr && notify->m_notKilled) { - notify->processChatterMessage (strVal); - } - } - } - graph.setBombPos (true); - } - else if (!bots.isBombPlanted () && strcmp (strVal, "#Bomb_Planted") == 0) { - bots.setBombPlanted (true); - - for (const auto ¬ify : bots) { - if (notify->m_notKilled) { - notify->clearSearchNodes (); - notify->clearTasks (); - - if (yb_radio_mode.int_ () == 2 && rg.chance (55) && notify->m_team == Team::CT) { - notify->pushChatterMessage (Chatter::WhereIsTheC4); - } - } - } - graph.setBombPos (); - } - else if (bot != nullptr && strcmp (strVal, "#Switch_To_BurstFire") == 0) { - bot->m_weaponBurstMode = BurstMode::On; - } - else if (bot != nullptr && strcmp (strVal, "#Switch_To_SemiAuto") == 0) { - bot->m_weaponBurstMode = BurstMode::Off; - } - } - break; - - case NetMsg::TeamInfo: - switch (m_msgBlock.state) { - case 0: - playerIndex = intVal; - break; - - case 1: - if (playerIndex > 0 && playerIndex <= maxClients ()) { - int team = Team::Unassigned; - - if (strVal[0] == 'U' && strVal[1] == 'N') { - team = Team::Unassigned; - } - else if (strVal[0] == 'T' && strVal[1] == 'E') { - team = Team::Terrorist; - } - else if (strVal[0] == 'C' && strVal[1] == 'T') { - team = Team::CT; - } - else if (strVal[0] == 'S' && strVal[1] == 'P') { - team = Team::Spectator; - } - auto &client = util.getClient (playerIndex - 1); - - client.team2 = team; - client.team = is (GameFlags::FreeForAll) ? playerIndex : team; - } - break; - } - break; - - case NetMsg::BarTime: - if (bot != nullptr && m_msgBlock.state == 0) { - if (intVal > 0) { - bot->m_hasProgressBar = true; // the progress bar on a hud - - if (mapIs (MapFlags::Demolition) && bots.isBombPlanted () && bot->m_team == Team::CT) { - bots.notifyBombDefuse (); - } - } - else if (intVal == 0) { - bot->m_hasProgressBar = false; // no progress bar or disappeared - } - } - break; - - case NetMsg::ItemStatus: - if (bot != nullptr && m_msgBlock.state == 0) { - bot->m_hasNVG = (intVal & ItemStatus::Nightvision) ? true : false; - bot->m_hasDefuser = (intVal & ItemStatus::DefusalKit) ? true : false; - } - break; - - case NetMsg::FlashBat: - if (bot != nullptr && m_msgBlock.state == 0) { - bot->m_flashLevel = static_cast (intVal); - } - break; - - case NetMsg::NVGToggle: - if (bot != nullptr && m_msgBlock.state == 0) { - bot->m_usesNVG = intVal > 0; - } - break; - - default: - logger.error ("Network message handler error. Call to unrecognized message id (%d).\n", m_msgBlock.msg); - } - ++m_msgBlock.state; // and finally update network message state -} - bool Game::loadCSBinary () { auto modname = getModName (); @@ -1257,82 +764,6 @@ void Game::slowFrame () { m_slowFrame = timebase () + 1.0f; } -void Game::beginMessage (edict_t *ent, int dest, int type) { - // store the message type in our own variables, since the GET_USER_MSG_ID () will just do a lot of strcmp()'s... - if (is (GameFlags::Metamod) && getMessageId (NetMsg::Money) == -1) { - - auto setMsgId = [&] (const char *name, NetMsg id) { - setMessageId (id, GET_USER_MSG_ID (PLID, name, nullptr)); - }; - setMsgId ("VGUIMenu", NetMsg::VGUI); - setMsgId ("ShowMenu", NetMsg::ShowMenu); - setMsgId ("WeaponList", NetMsg::WeaponList); - setMsgId ("CurWeapon", NetMsg::CurWeapon); - setMsgId ("AmmoX", NetMsg::AmmoX); - setMsgId ("AmmoPickup", NetMsg::AmmoPickup); - setMsgId ("Damage", NetMsg::Damage); - setMsgId ("Money", NetMsg::Money); - setMsgId ("StatusIcon", NetMsg::StatusIcon); - setMsgId ("DeathMsg", NetMsg::DeathMsg); - setMsgId ("ScreenFade", NetMsg::ScreenFade); - setMsgId ("HLTV", NetMsg::HLTV); - setMsgId ("TextMsg", NetMsg::TextMsg); - setMsgId ("TeamInfo", NetMsg::TeamInfo); - setMsgId ("BarTime", NetMsg::BarTime); - setMsgId ("SendAudio", NetMsg::SendAudio); - setMsgId ("SayText", NetMsg::SayText); - setMsgId ("FlashBat", NetMsg::FlashBat); - setMsgId ("Flashlight", NetMsg::Fashlight); - setMsgId ("NVGToggle", NetMsg::NVGToggle); - setMsgId ("ItemStatus", NetMsg::ItemStatus); - - if (is (GameFlags::HasBotVoice)) { - setMessageId (NetMsg::BotVoice, GET_USER_MSG_ID (PLID, "BotVoice", nullptr)); - } - } - - if ((!is (GameFlags::Legacy) || is (GameFlags::Xash3D)) && dest == MSG_SPEC && type == getMessageId (NetMsg::HLTV)) { - setCurrentMessageId (NetMsg::HLTV); - } - captureMessage (type, NetMsg::WeaponList); - - if (!isNullEntity (ent) && !(ent->v.flags & FL_DORMANT)) { - auto bot = bots[ent]; - - // is this message for a bot? - if (bot != nullptr) { - setCurrentMessageOwner (bot->index ()); - - // message handling is done in usermsg.cpp - captureMessage (type, NetMsg::VGUI); - captureMessage (type, NetMsg::CurWeapon); - captureMessage (type, NetMsg::AmmoX); - captureMessage (type, NetMsg::AmmoPickup); - captureMessage (type, NetMsg::Damage); - captureMessage (type, NetMsg::Money); - captureMessage (type, NetMsg::StatusIcon); - captureMessage (type, NetMsg::ScreenFade); - captureMessage (type, NetMsg::BarTime); - captureMessage (type, NetMsg::TextMsg); - captureMessage (type, NetMsg::ShowMenu); - captureMessage (type, NetMsg::FlashBat); - captureMessage (type, NetMsg::NVGToggle); - captureMessage (type, NetMsg::ItemStatus); - } - } - else if (dest == MSG_ALL) { - captureMessage (type, NetMsg::TeamInfo); - captureMessage (type, NetMsg::DeathMsg); - captureMessage (type, NetMsg::TextMsg); - - if (type == SVC_INTERMISSION) { - for (const auto &bot : bots) { - bot->m_notKilled = false; - } - } - } -} - void LightMeasure::initializeLightstyles () { // this function initializes lighting information... @@ -1376,7 +807,7 @@ void LightMeasure::updateLight (int style, char *value) { return; } - if (util.isEmptyStr (value)){ + if (strings.isEmpty (value)){ m_lightstyle[style].length = 0u; m_lightstyle[style].map[0] = '\0'; diff --git a/source/graph.cpp b/source/graph.cpp index 0e99bf0..1e259c3 100644 --- a/source/graph.cpp +++ b/source/graph.cpp @@ -1037,18 +1037,18 @@ void BotGraph::calculatePathRadius (int index) { for (float scanDistance = 32.0f; scanDistance < 128.0f; scanDistance += 16.0f) { start = path.origin; - game.makeVectors (nullvec); + auto null = nullvec; - direction = game.vec.forward * scanDistance; + direction = null.forward () * scanDistance; direction = direction.angles (); path.radius = scanDistance; for (float circleRadius = 0.0f; circleRadius < 360.0f; circleRadius += 20.0f) { - game.makeVectors (direction); + const auto &forward = direction.forward (); - Vector radiusStart = start + game.vec.forward * scanDistance; - Vector radiusEnd = start + game.vec.forward * scanDistance; + Vector radiusStart = start + forward * scanDistance; + Vector radiusEnd = start + forward * scanDistance; game.testHull (radiusStart, radiusEnd, TraceIgnore::Monsters, head_hull, nullptr, &tr); @@ -1067,7 +1067,7 @@ void BotGraph::calculatePathRadius (int index) { break; } - Vector dropStart = start + game.vec.forward * scanDistance; + Vector dropStart = start + forward * scanDistance; Vector dropEnd = dropStart - Vector (0.0f, 0.0f, scanDistance + 60.0f); game.testHull (dropStart, dropEnd, TraceIgnore::Monsters, head_hull, nullptr, &tr); @@ -1078,7 +1078,7 @@ void BotGraph::calculatePathRadius (int index) { break; } - dropStart = start - game.vec.forward * scanDistance; + dropStart = start - forward * scanDistance; dropEnd = dropStart - Vector (0.0f, 0.0f, scanDistance + 60.0f); game.testHull (dropStart, dropEnd, TraceIgnore::Monsters, head_hull, nullptr, &tr); @@ -1680,7 +1680,7 @@ void BotGraph::saveOldFormat () { const char *BotGraph::getOldFormatGraphName (bool isMemoryFile) { static String buffer; - buffer.assignf ("%s%s%s.pwf", getDataDirectory (isMemoryFile), util.isEmptyStr (yb_graph_subfolder.str ()) ? "" : yb_graph_subfolder.str (), game.getMapName ()); + buffer.assignf ("%s%s%s.pwf", getDataDirectory (isMemoryFile), strings.isEmpty (yb_graph_subfolder.str ()) ? "" : yb_graph_subfolder.str (), game.getMapName ()); if (File::exists (buffer)) { return buffer.chars (); @@ -1694,49 +1694,6 @@ float BotGraph::calculateTravelTime (float maxSpeed, const Vector &src, const Ve return (origin - src).length2d () / maxSpeed; } -bool BotGraph::isReachable (Bot *bot, int index) { - // this function return whether bot able to reach index node or not, depending on several factors. - - if (!bot || !exists (index)) { - return false; - } - const Vector &src = bot->pev->origin; - const Vector &dst = m_paths[index].origin; - - // is the destination close enough? - if ((dst - src).lengthSq () >= cr::square (320.0f)) { - return false; - } - float ladderDist = (dst - src).length2d (); - - TraceResult tr; - game.testLine (src, dst, TraceIgnore::Monsters, bot->ent (), &tr); - - // if node is visible from current position (even behind head)... - if (tr.flFraction >= 1.0f) { - - // it's should be not a problem to reach node inside water... - if (bot->pev->waterlevel == 2 || bot->pev->waterlevel == 3) { - return true; - } - - // check for ladder - bool nonLadder = !(m_paths[index].flags & NodeFlag::Ladder) || ladderDist > 16.0f; - - // is dest node higher than src? (62 is max jump height) - if (nonLadder && dst.z > src.z + 62.0f) { - return false; // can't reach this one - } - - // is dest node lower than src? - if (nonLadder && dst.z < src.z - 100.0f) { - return false; // can't reach this one - } - return true; - } - return false; -} - bool BotGraph::isNodeReacheable (const Vector &src, const Vector &destination) { TraceResult tr; @@ -2135,13 +2092,9 @@ void BotGraph::frame () { if (path.flags & NodeFlag::Crouch) { height = 18.0f; } - const Vector &source = Vector (path.origin.x, path.origin.y, path.origin.z + height); // source - - game.makeVectors (Vector (path.start.x, path.start.y, 0)); - const Vector &start = path.origin + game.vec.forward * 500.0f; // camp start - - game.makeVectors (Vector (path.end.x, path.end.y, 0)); - const Vector &end = path.origin + game.vec.forward * 500.0f; // camp end + const auto &source = Vector (path.origin.x, path.origin.y, path.origin.z + height); // source + const auto &start = path.origin + Vector (path.start.x, path.start.y, 0.0f).forward () * 500.0f; // camp start + const auto &end = path.origin + Vector (path.end.x, path.end.y, 0.0f).forward () * 500.0f; // camp end // draw it now game.drawLine (m_editor, source, start, 10, 0, Color (255, 0, 0), 200, 0, 10); diff --git a/source/interface.cpp b/source/interface.cpp index 8be2a2b..788f3db 100644 --- a/source/interface.cpp +++ b/source/interface.cpp @@ -612,7 +612,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { functionTable->pfnMessageBegin = [] (int msgDest, int msgType, const float *origin, edict_t *ed) { // this function called each time a message is about to sent. - game.beginMessage (ed, msgDest, msgType); + msgs.start (ed, msgDest, msgType); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); @@ -621,7 +621,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { }; functionTable->pfnMessageEnd = [] () { - game.resetMessages (); + msgs.stop (); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); @@ -631,7 +631,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { functionTable->pfnWriteByte = [] (int value) { // if this message is for a bot, call the client message function... - game.processMessages (reinterpret_cast (&value)); + msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); @@ -641,7 +641,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { functionTable->pfnWriteChar = [] (int value) { // if this message is for a bot, call the client message function... - game.processMessages (reinterpret_cast (&value)); + msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); @@ -651,7 +651,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { functionTable->pfnWriteShort = [] (int value) { // if this message is for a bot, call the client message function... - game.processMessages (reinterpret_cast (&value)); + msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); @@ -661,7 +661,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { functionTable->pfnWriteLong = [] (int value) { // if this message is for a bot, call the client message function... - game.processMessages (reinterpret_cast (&value)); + msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); @@ -671,7 +671,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { functionTable->pfnWriteAngle = [] (float value) { // if this message is for a bot, call the client message function... - game.processMessages (reinterpret_cast (&value)); + msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); @@ -681,7 +681,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { functionTable->pfnWriteCoord = [] (float value) { // if this message is for a bot, call the client message function... - game.processMessages (reinterpret_cast (&value)); + msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); @@ -691,7 +691,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { functionTable->pfnWriteString = [] (const char *sz) { // if this message is for a bot, call the client message function... - game.processMessages (reinterpret_cast (const_cast (sz))); + msgs.collect (sz); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); @@ -701,7 +701,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { functionTable->pfnWriteEntity = [] (int value) { // if this message is for a bot, call the client message function... - game.processMessages (reinterpret_cast (&value)); + msgs.collect (value); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); @@ -720,76 +720,13 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { // using pfnMessageBegin (), it will know what message ID number to send, and the engine will // know what to do, only for non-metamod version - if (game.is (GameFlags::Metamod)) { - RETURN_META_VALUE (MRES_IGNORED, 0); - } int message = engfuncs.pfnRegUserMsg (name, size); - if (strcmp (name, "VGUIMenu") == 0) { - game.setMessageId (NetMsg::VGUI, message); - } - else if (strcmp (name, "ShowMenu") == 0) { - game.setMessageId (NetMsg::ShowMenu, message); - } - else if (strcmp (name, "WeaponList") == 0) { - game.setMessageId (NetMsg::WeaponList, message); - } - else if (strcmp (name, "CurWeapon") == 0) { - game.setMessageId (NetMsg::CurWeapon, message); - } - else if (strcmp (name, "AmmoX") == 0) { - game.setMessageId (NetMsg::AmmoX, message); - } - else if (strcmp (name, "AmmoPickup") == 0) { - game.setMessageId (NetMsg::AmmoPickup, message); - } - else if (strcmp (name, "Damage") == 0) { - game.setMessageId (NetMsg::Damage, message); - } - else if (strcmp (name, "Money") == 0) { - game.setMessageId (NetMsg::Money, message); - } - else if (strcmp (name, "StatusIcon") == 0) { - game.setMessageId (NetMsg::StatusIcon, message); - } - else if (strcmp (name, "DeathMsg") == 0) { - game.setMessageId (NetMsg::DeathMsg, message); - } - else if (strcmp (name, "ScreenFade") == 0) { - game.setMessageId (NetMsg::ScreenFade, message); - } - else if (strcmp (name, "HLTV") == 0) { - game.setMessageId (NetMsg::HLTV, message); - } - else if (strcmp (name, "TextMsg") == 0) { - game.setMessageId (NetMsg::TextMsg, message); - } - else if (strcmp (name, "TeamInfo") == 0) { - game.setMessageId (NetMsg::TeamInfo, message); - } - else if (strcmp (name, "BarTime") == 0) { - game.setMessageId (NetMsg::BarTime, message); - } - else if (strcmp (name, "SendAudio") == 0) { - game.setMessageId (NetMsg::SendAudio, message); - } - else if (strcmp (name, "SayText") == 0) { - game.setMessageId (NetMsg::SayText, message); - } - else if (strcmp (name, "BotVoice") == 0) { - game.setMessageId (NetMsg::BotVoice, message); - } - else if (strcmp (name, "NVGToggle") == 0) { - game.setMessageId (NetMsg::NVGToggle, message); - } - else if (strcmp (name, "FlashBat") == 0) { - game.setMessageId (NetMsg::FlashBat, message); - } - else if (strcmp (name, "Flashlight") == 0) { - game.setMessageId (NetMsg::Fashlight, message); - } - else if (strcmp (name, "ItemStatus") == 0) { - game.setMessageId (NetMsg::ItemStatus, message); + // register message for our needs + msgs.registerMessage (name, message); + + if (game.is (GameFlags::Metamod)) { + RETURN_META_VALUE (MRES_SUPERCEDE, message); } return message; }; diff --git a/source/manager.cpp b/source/manager.cpp index ed2ed91..6d3e9b4 100644 --- a/source/manager.cpp +++ b/source/manager.cpp @@ -98,11 +98,11 @@ void BotManager::touchKillerEntity (Bot *bot) { } const auto &prop = conf.getWeaponProp (bot->m_currentWeapon); - m_killerEntity->v.classname = MAKE_STRING (prop.classname); + m_killerEntity->v.classname = MAKE_STRING (prop.classname.chars ()); m_killerEntity->v.dmg_inflictor = bot->ent (); KeyValueData kv; - kv.szClassName = const_cast (prop.classname); + kv.szClassName = const_cast (prop.classname.chars ()); kv.szKeyName = "damagetype"; kv.szValue = const_cast (strings.format ("%d", cr::bit (4))); kv.fHandled = FALSE; @@ -191,7 +191,7 @@ BotCreateResult BotManager::create (const String &name, int difficulty, int pers resultName = name; } - if (!util.isEmptyStr (yb_name_prefix.str ())) { + if (!strings.isEmpty (yb_name_prefix.str ())) { String prefixed; // temp buffer for storing modified name prefixed.assignf ("%s %s", yb_name_prefix.str (), resultName.chars ()); @@ -664,10 +664,10 @@ void BotManager::setWeaponMode (int selection) { void BotManager::listBots () { // this function list's bots currently playing on the server - ctrl.msg ("%-3.5s\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.4s\t%-3.4s", "index", "name", "personality", "team", "difficulty", "frags"); + ctrl.msg ("%-3.5s\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.4s\t%-3.4s\t%-3.5s", "index", "name", "personality", "team", "difficulty", "frags", "alive"); for (const auto &bot : bots) {; - ctrl.msg ("[%-3.1d]\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d", bot->index (), STRING (bot->pev->netname), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful", bot->m_team == Team::CT ? "CT" : "T", bot->m_difficulty, static_cast (bot->pev->frags)); + ctrl.msg ("[%-3.1d]\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d\t%-3.4s", bot->index (), STRING (bot->pev->netname), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful", bot->m_team == Team::CT ? "CT" : "T", bot->m_difficulty, static_cast (bot->pev->frags), bot->m_notKilled ? "yes" : "no"); } ctrl.msg ("%d bots", m_bots.length ()); } @@ -825,7 +825,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member) { char reject[256] = {0, }; MDLL_ClientConnect (bot, STRING (bot->v.netname), strings.format ("127.0.0.%d", clientIndex + 100), reject); - if (!util.isEmptyStr (reject)) { + if (!strings.isEmpty (reject)) { logger.error ("Server refused '%s' connection (%s)", STRING (bot->v.netname), reject); game.serverCommand ("kick \"%s\"", STRING (bot->v.netname)); // kick the bot player if the server refused it @@ -958,14 +958,71 @@ bool BotManager::isTeamStacked (int team) { } void BotManager::erase (Bot *bot) { - for (const auto &e : m_bots) { + for (auto &e : m_bots) { if (e.get () == bot) { + e.reset (); m_bots.remove (e); // remove from bots array + break; } } } +void BotManager::handleDeath (edict_t *killer, edict_t *victim) { + auto killerTeam = game.getTeam (killer); + auto victimTeam = game.getTeam (victim); + + if (yb_radio_mode.int_ () == 2) { + // need to send congrats on well placed shot + for (const auto ¬ify : bots) { + if (notify->m_notKilled && killerTeam == notify->m_team && killerTeam != victimTeam && killer != notify->ent () && notify->seesEntity (victim->v.origin)) { + if (!(killer->v.flags & FL_FAKECLIENT)) { + notify->processChatterMessage ("#Bot_NiceShotCommander"); + } + else { + notify->processChatterMessage ("#Bot_NiceShotPall"); + } + break; + } + } + } + Bot *killerBot = nullptr; + Bot *victimBot = nullptr; + + // notice nearby to victim teammates, that attacker is near + for (const auto ¬ify : bots) { + if (notify->m_seeEnemyTime + 2.0f < game.timebase () && notify->m_notKilled && notify->m_team == victimTeam && game.isNullEntity (notify->m_enemy) && killerTeam != victimTeam && util.isVisible (killer->v.origin, notify->ent ())) { + notify->m_actualReactionTime = 0.0f; + notify->m_seeEnemyTime = game.timebase (); + notify->m_enemy = killer; + notify->m_lastEnemy = killer; + notify->m_lastEnemyOrigin = killer->v.origin; + } + + if (notify->ent () == killer) { + killerBot = notify.get (); + } + else if (notify->ent () == victim) { + victimBot = notify.get (); + } + } + + // is this message about a bot who killed somebody? + if (killerBot != nullptr) { + killerBot->m_lastVictim = victim; + } + + // did a human kill a bot on his team? + else { + if (victimBot != nullptr) { + if (killerTeam == victimBot->m_team) { + victimBot->m_voteKickIndex = game.indexOfEntity (killer); + } + victimBot->m_notKilled = false; + } + } +} + void Bot::newRound () { // this function initializes a bot after creation & at the start of each round @@ -1200,7 +1257,7 @@ void Bot::kick () { // this function kick off one bot from the server. auto username = STRING (pev->netname); - if (!(pev->flags & FL_FAKECLIENT) || util.isEmptyStr (username)) { + if (!(pev->flags & FL_FAKECLIENT) || strings.isEmpty (username)) { return; } // clear fakeclient bit @@ -1291,7 +1348,7 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en Bot *bot = nullptr; if (util.findNearestPlayer (reinterpret_cast (&bot), ent, 300.0f, true, true, true)) { - bot->dropWeaponForUser (ent, util.isEmptyStr (strstr (arg, "c4")) ? false : true); + bot->dropWeaponForUser (ent, strings.isEmpty (strstr (arg, "c4")) ? false : true); } return; } @@ -1312,7 +1369,7 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en if (target != nullptr) { target->m_sayTextBuffer.entityIndex = game.indexOfPlayer (ent); - if (util.isEmptyStr (engfuncs.pfnCmd_Args ())) { + if (strings.isEmpty (engfuncs.pfnCmd_Args ())) { continue; } target->m_sayTextBuffer.sayText = engfuncs.pfnCmd_Args (); diff --git a/source/message.cpp b/source/message.cpp new file mode 100644 index 0000000..d924beb --- /dev/null +++ b/source/message.cpp @@ -0,0 +1,495 @@ +// +// 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: +// https://yapb.ru/license +// + +#include + +void MessageDispatcher::netMsgTextMsg () { + enum args { msg = 1, min = 2 }; + + // check the minimum states + if (m_args.length () < min) { + return; + } + + // bots chatter notification + const auto dispatchChatterMessage = [&] () -> void { + if (yb_radio_mode.int_ () == 2) { + auto notify = bots.findAliveBot (); + + if (notify && notify->m_notKilled) { + notify->processChatterMessage (m_args[msg].chars_); + + } + } + }; + + // lookup cached message + auto cached = m_textMsgCache[m_args[msg].chars_]; + + // check if we're need to handle message + if (!(cached & TextMsgCache::NeedHandle)) { + return; + } + + // reset bomb position + if (game.mapIs (MapFlags::Demolition)) { + graph.setBombPos (true); + } + + if (cached & TextMsgCache::Commencing) { + util.setNeedForWelcome (true); + } + else if (cached & TextMsgCache::CounterWin) { + bots.setLastWinner (Team::CT); // update last winner for economics + dispatchChatterMessage (); + } + else if (cached & TextMsgCache::RestartRound) { + bots.updateTeamEconomics (Team::CT, true); + bots.updateTeamEconomics (Team::Terrorist, true); + } + else if (cached & TextMsgCache::TerroristWin) { + bots.setLastWinner (Team::Terrorist); // update last winner for economics + dispatchChatterMessage (); + } + else if ((cached & TextMsgCache::BombPlanted) && !bots.isBombPlanted ()) { + bots.setBombPlanted (true); + + for (const auto ¬ify : bots) { + if (notify->m_notKilled) { + notify->clearSearchNodes (); + notify->clearTasks (); + + if (yb_radio_mode.int_ () == 2 && rg.chance (55) && notify->m_team == Team::CT) { + notify->pushChatterMessage (Chatter::WhereIsTheC4); + } + } + } + graph.setBombPos (); + } + + // check for burst fire message + if (m_bot) { + if (cached & TextMsgCache::BurstOn) { + m_bot->m_weaponBurstMode = BurstMode::On; + } + else if (cached & TextMsgCache::BurstOff) { + m_bot->m_weaponBurstMode = BurstMode::Off; + } + } +} + +void MessageDispatcher::netMsgVGUIMenu () { + // this message is sent when a VGUI menu is displayed. + + enum args { menu = 0, min = 1 }; + + // check the minimum states or existance of bot + if (m_args.length () < min || !m_bot) { + return; + } + + switch (m_args[menu].long_) { + case GuiMenu::TeamSelect: + m_bot->m_startAction = BotMsg::TeamSelect; + break; + + case GuiMenu::TerroristSelect: + case GuiMenu::CTSelect: + m_bot->m_startAction = BotMsg::ClassSelect; + break; + } +} + +void MessageDispatcher::netMsgShowMenu () { + // this message is sent when a text menu is displayed. + + enum args { menu = 3, min = 4 }; + + // check the minimum states or existance of bot + if (m_args.length () < min || !m_bot) { + return; + } + m_bot->m_startAction = m_showMenuCache[m_args[menu].chars_]; +} + +void MessageDispatcher::netMsgWeaponList () { + // this message is sent when a client joins the game. All of the weapons are sent with the weapon ID and information about what ammo is used. + + enum args { classname = 0, ammo_index_1 = 1, max_ammo_1 = 2, slot = 5, slot_pos = 6, id = 7, flags = 8, min = 9 }; + + // check the minimum states + if (m_args.length () < min) { + return; + } + + // register prop + WeaponProp prop { + m_args[classname].chars_, + m_args[ammo_index_1].long_, + m_args[max_ammo_1].long_, + m_args[slot].long_, + m_args[slot_pos].long_, + m_args[id].long_, + m_args[flags].long_ + }; + conf.setWeaponProp (cr::move (prop)); // store away this weapon with it's ammo information... +} + +void MessageDispatcher::netMsgCurWeapon () { + // this message is sent when a weapon is selected (either by the bot chosing a weapon or by the server auto assigning the bot a weapon). In CS it's also called when Ammo is increased/decreased + + enum args { state = 0, id = 1, clip = 2, min = 3 }; + + // check the minimum states + if (m_args.length () < min || !m_bot) { + return; + } + + if (m_args[id].long_ < kMaxWeapons) { + if (m_args[state].long_ != 0) { + m_bot->m_currentWeapon = m_args[id].long_; + } + + // ammo amount decreased ? must have fired a bullet... + if (m_args[id].long_ == m_bot->m_currentWeapon && m_bot->m_ammoInClip[m_args[id].long_] > m_args[clip].long_) { + m_bot->m_timeLastFired = game.timebase (); // remember the last bullet time + } + m_bot->m_ammoInClip[m_args[id].long_] = m_args[clip].long_; + } +} + +void MessageDispatcher::netMsgAmmoX () { + // this message is sent whenever ammo amounts are adjusted (up or down). NOTE: Logging reveals that CS uses it very unreliable! + + enum args { index = 0, value = 1, min = 2 }; + + // check the minimum states + if (m_args.length () < min || !m_bot) { + return; + } + m_bot->m_ammo[m_args[index].long_] = m_args[value].long_; // store it away +} + +void MessageDispatcher::netMsgAmmoPickup () { + // this message is sent when the bot picks up some ammo (AmmoX messages are also sent so this message is probably + // not really necessary except it allows the HUD to draw pictures of ammo that have been picked up. The bots + // don't really need pictures since they don't have any eyes anyway. + + enum args { index = 0, value = 1, min = 2 }; + + // check the minimum states + if (m_args.length () < min || !m_bot) { + return; + } + m_bot->m_ammo[m_args[index].long_] = m_args[value].long_; // store it away +} + +void MessageDispatcher::netMsgDamage () { + // this message gets sent when the bots are getting damaged. + + enum args { armor = 0, health = 1, bits = 2, min = 3 }; + + // check the minimum states + if (m_args.length () < min || !m_bot) { + return; + } + + // handle damage if any + if (m_args[armor].long_ > 0 || m_args[health].long_) { + m_bot->processDamage (m_bot->pev->dmg_inflictor, m_args[health].long_, m_args[armor].long_, m_args[bits].long_); + } +} + +void MessageDispatcher::netMsgMoney () { + // this message gets sent when the bots money amount changes + + enum args { money = 0, min = 1 }; + + // check the minimum states + if (m_args.length () < min || !m_bot) { + return; + } + m_bot->m_moneyAmount = m_args[money].long_; +} + +void MessageDispatcher::netMsgStatusIcon () { + enum args { enabled = 0, icon = 1, min = 2 }; + + // check the minimum states + if (m_args.length () < min || !m_bot) { + return; + } + // lookup cached icon + auto cached = m_statusIconCache[m_args[icon].chars_]; + + // check if we're need to handle message + if (!(cached & TextMsgCache::NeedHandle)) { + return; + } + + // handle cases + if (cached & StatusIconCache::BuyZone) { + m_bot->m_inBuyZone = (m_args[enabled].long_ != 0); + + // try to equip in buyzone + m_bot->processBuyzoneEntering (BuyState::PrimaryWeapon); + } + else if (cached & StatusIconCache::VipSafety) { + m_bot->m_inVIPZone = (m_args[enabled].long_ != 0); + } + else if (cached & StatusIconCache::C4) { + m_bot->m_inBombZone = (m_args[enabled].long_ == 2); + } +} + +void MessageDispatcher::netMsgDeathMsg () { + // this message gets sent when player kills player + + enum args { killer = 0, victim = 1, min = 2 }; + + // check the minimum states + if (m_args.length () < min) { + return; + } + + auto killerEntity = game.entityOfIndex (m_args[killer].long_); + auto victimEntity = game.entityOfIndex (m_args[victim].long_); + + if (game.isNullEntity (killerEntity) || game.isNullEntity (victimEntity) || victimEntity == killerEntity) { + return; + } + bots.handleDeath (killerEntity, victimEntity); +} + +void MessageDispatcher::netMsgScreenFade () { + // this message gets sent when the screen fades (flashbang) + + enum args { r = 3, g = 4, b = 5, alpha = 6, min = 7 }; + + // check the minimum states + if (m_args.length () < min || !m_bot) { + return; + } + + // screen completely faded ? + if (m_args[r].long_ >= 255 && m_args[g].long_ >= 255 && m_args[b].long_ >= 255 && m_args[alpha].long_ > 170) { + m_bot->processBlind (m_args[alpha].long_); + } +} + +void MessageDispatcher::netMsgHLTV () { + // this message gets sent when new round is started in modern cs versions + + enum args { players = 0, fov = 1, min = 2 }; + + // check the minimum states + if (m_args.length () < min) { + return; + } + + // need to start new round ? (we're tracking FOV reset message) + if (m_args[players].long_ == 0 && m_args[fov].long_ == 0) { + bots.initRound (); + } +} + +void MessageDispatcher::netMsgTeamInfo () { + // this message gets sent when player team index is changed + + enum args { index = 0, team = 1, min = 2 }; + + // check the minimum states + if (m_args.length () < min) { + return; + } + auto &client = util.getClient (m_args[index].long_ - 1); + + // update player team + client.team2 = m_teamInfoCache[m_args[team].chars_]; // update real team + client.team = game.is (GameFlags::FreeForAll) ? m_args[index].long_ : client.team2; +} + +void MessageDispatcher::netMsgBarTime () { + enum args { enabled = 0, min = 1 }; + + // check the minimum states + if (m_args.length () < min || !m_bot) { + return; + } + + // check if has progress bar + if (m_args[enabled].long_ > 0) { + m_bot->m_hasProgressBar = true; // the progress bar on a hud + + // notify bots about defusing has started + if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_bot->m_team == Team::CT) { + bots.notifyBombDefuse (); + } + } + else { + m_bot->m_hasProgressBar = false; // no progress bar or disappeared + } +} + +void MessageDispatcher::netMsgItemStatus () { + enum args { value = 0, min = 1 }; + + // check the minimum states + if (m_args.length () < min || !m_bot) { + return; + } + auto mask = m_args[value].long_; + + m_bot->m_hasNVG = !!(mask & ItemStatus::Nightvision); + m_bot->m_hasDefuser = !!(mask & ItemStatus::DefusalKit); +} + +void MessageDispatcher::netMsgNVGToggle () { + enum args { value = 0, min = 1 }; + + // check the minimum states + if (m_args.length () < min || !m_bot) { + return; + } + m_bot->m_usesNVG = m_args[value].long_ > 0; +} + +void MessageDispatcher::netMsgFlashBat () { + enum args { value = 0, min = 1 }; + + // check the minimum states + if (m_args.length () < min || !m_bot) { + return; + } + m_bot->m_flashLevel = m_args[value].float_; +} + +MessageDispatcher::MessageDispatcher () { + + // register wanted message + auto pushWanted = [&] (const String &name, NetMsg id, MsgFunc handler) -> void { + m_wanted[name] = id; + m_handlers[id] = handler; + }; + + // we want to handle next messages + pushWanted ("TextMsg", NetMsg::TextMsg, &MessageDispatcher::netMsgTextMsg); + pushWanted ("VGUIMenu", NetMsg::VGUIMenu, &MessageDispatcher::netMsgVGUIMenu); + pushWanted ("ShowMenu", NetMsg::ShowMenu, &MessageDispatcher::netMsgShowMenu); + pushWanted ("WeaponList", NetMsg::WeaponList, &MessageDispatcher::netMsgWeaponList); + pushWanted ("CurWeapon", NetMsg::CurWeapon, &MessageDispatcher::netMsgCurWeapon); + pushWanted ("AmmoX", NetMsg::AmmoX, &MessageDispatcher::netMsgAmmoX); + pushWanted ("AmmoPickup", NetMsg::AmmoPickup, &MessageDispatcher::netMsgAmmoPickup); + pushWanted ("Damage", NetMsg::Damage, &MessageDispatcher::netMsgDamage); + pushWanted ("Money", NetMsg::Money, &MessageDispatcher::netMsgMoney); + pushWanted ("StatusIcon", NetMsg::StatusIcon, &MessageDispatcher::netMsgStatusIcon); + pushWanted ("DeathMsg", NetMsg::DeathMsg, &MessageDispatcher::netMsgDeathMsg); + pushWanted ("ScreenFade", NetMsg::ScreenFade, &MessageDispatcher::netMsgScreenFade); + pushWanted ("HLTV", NetMsg::HLTV, &MessageDispatcher::netMsgHLTV); + pushWanted ("TeamInfo", NetMsg::TeamInfo, &MessageDispatcher::netMsgTeamInfo); + pushWanted ("BarTime", NetMsg::BarTime, &MessageDispatcher::netMsgBarTime); + pushWanted ("ItemStatus", NetMsg::ItemStatus, &MessageDispatcher::netMsgItemStatus); + pushWanted ("NVGToggle", NetMsg::NVGToggle, &MessageDispatcher::netMsgNVGToggle); + pushWanted ("FlashBat", NetMsg::FlashBat, &MessageDispatcher::netMsgFlashBat); + + // we're need next messages IDs but we're won't handle them, so they will be removed from wanted list as soon as they get engine IDs + pushWanted ("BotVoice", NetMsg::BotVoice, nullptr); + pushWanted ("SendAudio", NetMsg::SendAudio, nullptr); + + // register text msg cache + m_textMsgCache["#CTs_Win"] = TextMsgCache::NeedHandle | TextMsgCache::CounterWin; + m_textMsgCache["#Bomb_Defused"] = TextMsgCache::NeedHandle; + m_textMsgCache["#Bomb_Planted"] = TextMsgCache::NeedHandle | TextMsgCache::BombPlanted; + m_textMsgCache["#Terrorists_Win"] = TextMsgCache::NeedHandle | TextMsgCache::TerroristWin; + m_textMsgCache["#Round_Draw"] = TextMsgCache::NeedHandle; + m_textMsgCache["#All_Hostages_Rescued"] = TextMsgCache::NeedHandle; + m_textMsgCache["#Target_Saved"] = TextMsgCache::NeedHandle; + m_textMsgCache["#Hostages_Not_Rescued"] = TextMsgCache::NeedHandle; + m_textMsgCache["#Terrorists_Not_Escaped"] = TextMsgCache::NeedHandle; + m_textMsgCache["#VIP_Not_Escaped"] = TextMsgCache::NeedHandle; + m_textMsgCache["#Escaping_Terrorists_Neutralized"] = TextMsgCache::NeedHandle; + m_textMsgCache["#VIP_Assassinated"] = TextMsgCache::NeedHandle; + m_textMsgCache["#VIP_Escaped"] = TextMsgCache::NeedHandle; + m_textMsgCache["#Terrorists_Escaped"] = TextMsgCache::NeedHandle; + m_textMsgCache["#CTs_PreventEscape"] = TextMsgCache::NeedHandle; + m_textMsgCache["#Target_Bombed"] = TextMsgCache::NeedHandle; + m_textMsgCache["#Game_Commencing"] = TextMsgCache::NeedHandle | TextMsgCache::Commencing; + m_textMsgCache["#Game_will_restart_in"] = TextMsgCache::NeedHandle | TextMsgCache::RestartRound; + m_textMsgCache["#Switch_To_BurstFire"] = TextMsgCache::NeedHandle | TextMsgCache::BurstOn; + m_textMsgCache["#Switch_To_SemiAuto"] = TextMsgCache::NeedHandle | TextMsgCache::BurstOff; + + // register show menu cache + m_showMenuCache["#Team_Select"] = BotMsg::TeamSelect; + m_showMenuCache["#Team_Select_Spect"] = BotMsg::TeamSelect; + m_showMenuCache["#IG_Team_Select_Spect"] = BotMsg::TeamSelect; + m_showMenuCache["#IG_Team_Select"] = BotMsg::TeamSelect; + m_showMenuCache["#IG_VIP_Team_Select"] = BotMsg::TeamSelect; + m_showMenuCache["#IG_VIP_Team_Select_Spect"] = BotMsg::TeamSelect; + m_showMenuCache["#Terrorist_Select"] = BotMsg::ClassSelect; + m_showMenuCache["#CT_Select"] = BotMsg::ClassSelect; + + // register status icon cache + m_statusIconCache["buyzone"] = StatusIconCache::NeedHandle | StatusIconCache::BuyZone; + m_statusIconCache["vipsafety"] = StatusIconCache::NeedHandle | StatusIconCache::VipSafety; + m_statusIconCache["c4"] = StatusIconCache::NeedHandle | StatusIconCache::C4; + + // register team info cache + m_teamInfoCache["TERRORIST"] = Team::Terrorist; + m_teamInfoCache["UNASSIGNED"] = Team::Unassigned; + m_teamInfoCache["SPECTATOR"] = Team::Spectator; + m_teamInfoCache["CT"] = Team::CT; +} + +void MessageDispatcher::registerMessage (const String &name, int32 id) { + if (!m_wanted.exists (name)) { + return; + } + m_maps[m_wanted[name]] = id; // add message from engine RegUserMsg +} + +void MessageDispatcher::start (edict_t *ent, int32 dest, int32 type) { + m_current = NetMsg::None; + + // search if we need to handle this message + for (const auto &msg : m_maps) { + if (msg.value == type && m_handlers[msg.key]) { + m_current = msg.key; + break; + } + } + + // no messagem no processing + if (m_current == NetMsg::None) { + return; + } + + // broadcast message ? + if (dest == MSG_ALL || dest == MSG_SPEC || dest == MSG_BROADCAST) { + m_broadcast = true; + } + + // message for bot bot? + if (ent && (ent->v.flags & FL_FAKECLIENT)) { + m_bot = bots[ent]; + + if (!m_bot) { + m_current = NetMsg::None; + return; + } + } + m_args.clear (); // clear previous args +} + +void MessageDispatcher::stop () { + if (m_current == NetMsg::None) { + return; + } + (this->*m_handlers[m_current]) (); + m_current = NetMsg::None; +} diff --git a/source/navigate.cpp b/source/navigate.cpp index f0a62b1..17f2383 100644 --- a/source/navigate.cpp +++ b/source/navigate.cpp @@ -322,6 +322,11 @@ bool Bot::doPlayerAvoidance (const Vector &normal) { if (toProj > c) { m_moveSpeed = -pev->maxspeed; + + if (m_avoid->v.button & IN_DUCK) { + m_moveSpeed = pev->maxspeed; + pev->button |= IN_JUMP; + } return true; } else if (toProj < -c) { @@ -331,12 +336,12 @@ bool Bot::doPlayerAvoidance (const Vector &normal) { if (latProj >= c) { pev->button |= IN_MOVELEFT; - setStrafeSpeed (normal, pev->maxspeed); + setStrafeSpeed (normal, -pev->maxspeed); return true; } else if (latProj <= -c) { pev->button |= IN_MOVERIGHT; - setStrafeSpeed (normal, -pev->maxspeed); + setStrafeSpeed (normal, pev->maxspeed); return true; } return false; @@ -349,7 +354,11 @@ bool Bot::doPlayerAvoidance (const Vector &normal) { void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { m_isStuck = false; - + + // if avoiding someone do not consider stuck + if (doPlayerAvoidance (dirNormal)) { + return; + } TraceResult tr; // Standing still, no need to check? @@ -429,10 +438,11 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { state[i + 1] = 0; // to start strafing, we have to first figure out if the target is on the left side or right side - game.makeVectors (m_moveAngles); + Vector right, forward; + m_moveAngles.buildVectors (&forward, &right, nullptr); Vector dirToPoint = (pev->origin - m_destOrigin).normalize2d (); - Vector rightSide = game.vec.right.normalize2d (); + Vector rightSide = right.normalize2d (); bool dirRight = false; bool dirLeft = false; @@ -445,10 +455,10 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { else { dirLeft = true; } - const Vector &testDir = m_moveSpeed > 0.0f ? game.vec.forward : -game.vec.forward; + const Vector &testDir = m_moveSpeed > 0.0f ? forward : -forward; // now check which side is blocked - src = pev->origin + game.vec.right * 32.0f; + src = pev->origin + right * 32.0f; dst = src + testDir * 32.0f; game.testHull (src, dst, TraceIgnore::Monsters, head_hull, ent (), &tr); @@ -456,7 +466,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { if (tr.flFraction != 1.0f) { blockedRight = true; } - src = pev->origin - game.vec.right * 32.0f; + src = pev->origin - right * 32.0f; dst = src + testDir * 32.0f; game.testHull (src, dst, TraceIgnore::Monsters, head_hull, ent (), &tr); @@ -502,16 +512,16 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } if (seesEntity (m_destOrigin)) { - game.makeVectors (m_moveAngles); + const auto &right = m_moveAngles.right (); src = getEyesPos (); - src = src + game.vec.right * 15.0f; + src = src + right * 15.0f; game.testLine (src, m_destOrigin, TraceIgnore::Everything, ent (), &tr); if (tr.flFraction >= 1.0f) { src = getEyesPos (); - src = src - game.vec.right * 15.0f; + src = src - right * 15.0f; game.testLine (src, m_destOrigin, TraceIgnore::Everything, ent (), &tr); @@ -622,11 +632,6 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } } } - - // avoid players if not already stuck - if (!m_isStuck) { - doPlayerAvoidance (dirNormal); - } } bool Bot::updateNavigation () { @@ -640,9 +645,8 @@ bool Bot::updateNavigation () { m_pathOrigin = m_path->origin; // if wayzone radios non zero vary origin a bit depending on the body angles - if (m_path->radius > 0) { - game.makeVectors (Vector (pev->angles.x, cr::normalizeAngles (pev->angles.y + rg.float_ (-90.0f, 90.0f)), 0.0f)); - m_pathOrigin = m_pathOrigin + game.vec.forward * rg.float_ (0, m_path->radius); + if (m_path->radius > 0.0f) { + m_pathOrigin = m_pathOrigin + Vector (pev->angles.x, cr::normalizeAngles (pev->angles.y + rg.float_ (-90.0f, 90.0f)), 0.0f).forward () * rg.float_ (0, m_path->radius); } m_navTimeset = game.timebase (); } @@ -1093,13 +1097,15 @@ bool Bot::updateNavigation () { else if (m_currentTravelFlags & PathFlag::Jump) { desiredDistance = 0.0f; } - else if (isOccupiedPoint (m_currentNodeIndex)) { - desiredDistance = 120.0f; - } else { desiredDistance = m_path->radius; } + // if desired distance is big enough and used by someone, increase desired radius by twice, and mark it as reached... + if (desiredDistance >= m_path->radius && isOccupiedPoint (m_currentNodeIndex)) { + desiredDistance *= 2.0f; + } + // check if node has a special travelflag, so they need to be reached more precisely for (const auto &link : m_path->links) { if (link.flags != 0) { @@ -1201,7 +1207,7 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: return 0.0f; } auto cost = static_cast (graph.getDangerDamage (team, currentIndex, currentIndex) + graph.getHighestDamageForTeam (team)); - Path ¤t = graph[currentIndex]; + const auto ¤t = graph[currentIndex]; for (auto &neighbour : current.links) { if (neighbour.index != kInvalidNodeIndex) { @@ -1217,7 +1223,7 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: // least kills and number of nodes to goal for a team auto gfunctionKillsDistCTWithHostage = [&gfunctionKillsDist] (int team, int currentIndex, int parentIndex) -> float { - Path ¤t = graph[currentIndex]; + const auto ¤t = graph[currentIndex]; if (current.flags & NodeFlag::NoHostage) { return 65355.0f; @@ -1231,7 +1237,7 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: // least kills to goal for a team auto gfunctionKills = [] (int team, int currentIndex, int) -> float { auto cost = static_cast (graph.getDangerDamage (team, currentIndex, currentIndex)); - Path ¤t = graph[currentIndex]; + const auto ¤t = graph[currentIndex]; for (auto &neighbour : current.links) { if (neighbour.index != kInvalidNodeIndex) { @@ -1250,7 +1256,7 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: if (parentIndex == kInvalidNodeIndex) { return 0.0f; } - Path ¤t = graph[currentIndex]; + const auto ¤t = graph[currentIndex]; if (current.flags & NodeFlag::NoHostage) { return 65355.0f; @@ -1265,8 +1271,8 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: if (parentIndex == kInvalidNodeIndex) { return 0.0f; } - Path &parent = graph[parentIndex]; - Path ¤t = graph[currentIndex]; + const auto &parent = graph[parentIndex]; + const auto ¤t = graph[currentIndex]; for (const auto &link : parent.links) { if (link.index == currentIndex) { @@ -1281,7 +1287,7 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: }; auto gfunctionPathDistWithHostage = [&gfunctionPathDist] (int, int currentIndex, int parentIndex) -> float { - Path ¤t = graph[currentIndex]; + const auto ¤t = graph[currentIndex]; if (current.flags & NodeFlag::NoHostage) { return 65355.0f; @@ -1294,8 +1300,8 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: // square distance heuristic auto hfunctionPathDist = [] (int index, int, int goalIndex) -> float { - Path &start = graph[index]; - Path &goal = graph[goalIndex]; + const auto &start = graph[index]; + const auto &goal = graph[goalIndex]; float x = cr::abs (start.origin.x - goal.origin.x); float y = cr::abs (start.origin.y - goal.origin.y); @@ -1442,7 +1448,9 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: } while (currentIndex != kInvalidNodeIndex); + // reverse path for path follower m_pathWalk.reverse (); + return; } auto curRoute = &m_routes[currentIndex]; @@ -1576,7 +1584,7 @@ bool Bot::findBestNearestNode () { } // ignore non-reacheable nodes... - if (!graph.isReachable (this, at)) { + if (!isReachableNode (at)) { continue; } @@ -1770,7 +1778,7 @@ int Bot::findNearestNode () { if (distance < minimum) { // if bot doing navigation, make sure node really visible and not too high - if ((m_currentNodeIndex != kInvalidNodeIndex && graph.isVisible (m_currentNodeIndex, at)) || graph.isReachable (this, at)) { + if ((m_currentNodeIndex != kInvalidNodeIndex && graph.isVisible (m_currentNodeIndex, at)) || isReachableNode (at)) { index = at; minimum = distance; } @@ -2229,8 +2237,7 @@ bool Bot::advanceMovement () { // if wayzone radius non zero vary origin a bit depending on the body angles if (m_path->radius > 0.0f) { - game.makeVectors (Vector (pev->angles.x, cr::normalizeAngles (pev->angles.y + rg.float_ (-90.0f, 90.0f)), 0.0f)); - m_pathOrigin = m_pathOrigin + game.vec.forward * rg.float_ (0.0f, m_path->radius); + m_pathOrigin = m_pathOrigin + Vector (pev->angles.x, cr::normalizeAngles (pev->angles.y + rg.float_ (-90.0f, 90.0f)), 0.0f).forward () * rg.float_ (0.0f, m_path->radius); } if (isOnLadder ()) { @@ -2254,7 +2261,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { Vector src = getEyesPos (); Vector forward = src + normal * 24.0f; - game.makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); + const auto &right = Vector (0.0f, pev->angles.y, 0.0f).right (); auto checkDoor = [] (TraceResult *tr) { if (!game.mapIs (MapFlags::HasDoors)) { @@ -2276,8 +2283,8 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { // bot's head is clear, check at shoulder level... // trace from the bot's shoulder left diagonal forward to the right shoulder... - src = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) - game.vec.right * -16.0f; - forward = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) + game.vec.right * 16.0f + normal * 24.0f; + src = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) - right * -16.0f; + forward = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) + right * 16.0f + normal * 24.0f; game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); @@ -2288,8 +2295,8 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { // bot's head is clear, check at shoulder level... // trace from the bot's shoulder right diagonal forward to the left shoulder... - src = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) + game.vec.right * 16.0f; - forward = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) - game.vec.right * -16.0f + normal * 24.0f; + src = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) + right * 16.0f; + forward = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) - right * -16.0f + normal * 24.0f; game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); @@ -2321,8 +2328,8 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { } else { // trace from the left waist to the right forward waist pos - src = pev->origin + Vector (0.0f, 0.0f, -17.0f) - game.vec.right * -16.0f; - forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) + game.vec.right * 16.0f + normal * 24.0f; + src = pev->origin + Vector (0.0f, 0.0f, -17.0f) - right * -16.0f; + forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) + right * 16.0f + normal * 24.0f; // trace from the bot's waist straight forward... game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); @@ -2333,8 +2340,8 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { } // trace from the left waist to the right forward waist pos - src = pev->origin + Vector (0.0f, 0.0f, -17.0f) + game.vec.right * 16.0f; - forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) - game.vec.right * -16.0f + normal * 24.0f; + src = pev->origin + Vector (0.0f, 0.0f, -17.0f) + right * 16.0f; + forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) - right * -16.0f + normal * 24.0f; game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); @@ -2346,28 +2353,28 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { return false; // bot can move forward, return false } -#ifdef DEAD_CODE bool Bot::canStrafeLeft (TraceResult *tr) { // this function checks if bot can move sideways - makeVectors (pev->v_angle); + Vector right, forward; + pev->v_angle.buildVectors (&forward, &right, nullptr); Vector src = pev->origin; - Vector left = src - game.vec.right * -40.0f; + Vector dest = src - right * -40.0f; // trace from the bot's waist straight left... - TraceLine (src, left, true, ent (), tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), tr); // check if the trace hit something... if (tr->flFraction < 1.0f) { return false; // bot's body will hit something } - src = left; - left = left + game.vec.forward * 40.0f; + src = dest; + dest = dest + forward * 40.0f; // trace from the strafe pos straight forward... - TraceLine (src, left, true, ent (), tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), tr); // check if the trace hit something... if (tr->flFraction < 1.0f) { @@ -2379,23 +2386,24 @@ bool Bot::canStrafeLeft (TraceResult *tr) { bool Bot::canStrafeRight (TraceResult *tr) { // this function checks if bot can move sideways - makeVectors (pev->v_angle); + Vector right, forward; + pev->v_angle.buildVectors (&forward, &right, nullptr); Vector src = pev->origin; - Vector right = src + game.vec.right * 40.0f; + Vector dest = src + right * 40.0f; // trace from the bot's waist straight right... - TraceLine (src, right, true, ent (), tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), tr); // check if the trace hit something... if (tr->flFraction < 1.0f) { return false; // bot's body will hit something } - src = right; - right = right + game.vec.forward * 40.0f; + src = dest; + dest = dest + forward * 40.0f; // trace from the strafe pos straight forward... - TraceLine (src, right, true, ent (), tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), tr); // check if the trace hit something... if (tr->flFraction < 1.0f) { @@ -2404,8 +2412,6 @@ bool Bot::canStrafeRight (TraceResult *tr) { return true; } -#endif - bool Bot::canJumpUp (const Vector &normal) { // this function check if bot can jump over some obstacle @@ -2415,9 +2421,7 @@ bool Bot::canJumpUp (const Vector &normal) { if (!isOnFloor () && (isOnLadder () || !isInWater ())) { return false; } - - // convert current view angle to vectors for traceline math... - game.makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); + const auto &right = Vector (0.0f, pev->angles.y, 0.0f).right (); // convert current view angle to vectors for traceline math... // check for normal jump height first... Vector src = pev->origin + Vector (0.0f, 0.0f, -36.0f + 45.0f); @@ -2427,7 +2431,7 @@ bool Bot::canJumpUp (const Vector &normal) { game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); if (tr.flFraction < 1.0f) { - return doneCanJumpUp (normal); + return doneCanJumpUp (normal, right); } else { // now trace from jump height upward to check for obstructions... @@ -2442,7 +2446,7 @@ bool Bot::canJumpUp (const Vector &normal) { } // now check same height to one side of the bot... - src = pev->origin + game.vec.right * 16.0f + Vector (0.0f, 0.0f, -36.0f + 45.0f); + src = pev->origin + right * 16.0f + Vector (0.0f, 0.0f, -36.0f + 45.0f); dest = src + normal * 32.0f; // trace a line forward at maximum jump height... @@ -2450,7 +2454,7 @@ bool Bot::canJumpUp (const Vector &normal) { // if trace hit something, return false if (tr.flFraction < 1.0f) { - return doneCanJumpUp (normal); + return doneCanJumpUp (normal, right); } // now trace from jump height upward to check for obstructions... @@ -2465,7 +2469,7 @@ bool Bot::canJumpUp (const Vector &normal) { } // now check same height on the other side of the bot... - src = pev->origin + (-game.vec.right * 16.0f) + Vector (0.0f, 0.0f, -36.0f + 45.0f); + src = pev->origin + (-right * 16.0f) + Vector (0.0f, 0.0f, -36.0f + 45.0f); dest = src + normal * 32.0f; // trace a line forward at maximum jump height... @@ -2473,7 +2477,7 @@ bool Bot::canJumpUp (const Vector &normal) { // if trace hit something, return false if (tr.flFraction < 1.0f) { - return doneCanJumpUp (normal); + return doneCanJumpUp (normal, right); } // now trace from jump height upward to check for obstructions... @@ -2486,7 +2490,7 @@ bool Bot::canJumpUp (const Vector &normal) { return tr.flFraction > 1.0f; } -bool Bot::doneCanJumpUp (const Vector &normal) { +bool Bot::doneCanJumpUp (const Vector &normal, const Vector &right) { // use center of the body first... maximum duck jump height is 62, so check one unit above that (63) Vector src = pev->origin + Vector (0.0f, 0.0f, -36.0f + 63.0f); Vector dest = src + normal * 32.0f; @@ -2513,7 +2517,7 @@ bool Bot::doneCanJumpUp (const Vector &normal) { } // now check same height to one side of the bot... - src = pev->origin + game.vec.right * 16.0f + Vector (0.0f, 0.0f, -36.0f + 63.0f); + src = pev->origin + right * 16.0f + Vector (0.0f, 0.0f, -36.0f + 63.0f); dest = src + normal * 32.0f; // trace a line forward at maximum jump height... @@ -2536,7 +2540,7 @@ bool Bot::doneCanJumpUp (const Vector &normal) { } // now check same height on the other side of the bot... - src = pev->origin + (-game.vec.right * 16.0f) + Vector (0.0f, 0.0f, -36.0f + 63.0f); + src = pev->origin + (-right * 16.0f) + Vector (0.0f, 0.0f, -36.0f + 63.0f); dest = src + normal * 32.0f; // trace a line forward at maximum jump height... @@ -2563,9 +2567,6 @@ bool Bot::canDuckUnder (const Vector &normal) { TraceResult tr; Vector baseHeight; - // convert current view angle to vectors for TraceLine math... - game.makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); - // use center of the body first... if (pev->flags & FL_DUCKING) { baseHeight = pev->origin + Vector (0.0f, 0.0f, -17.0f); @@ -2585,8 +2586,11 @@ bool Bot::canDuckUnder (const Vector &normal) { return false; } + // convert current view angle to vectors for TraceLine math... + const auto &right = Vector (0.0f, pev->angles.y, 0.0f).right (); + // now check same height to one side of the bot... - src = baseHeight + game.vec.right * 16.0f; + src = baseHeight + right * 16.0f; dest = src + normal * 32.0f; // trace a line forward at duck height... @@ -2598,7 +2602,7 @@ bool Bot::canDuckUnder (const Vector &normal) { } // now check same height on the other side of the bot... - src = baseHeight + (-game.vec.right * 16.0f); + src = baseHeight + (-right * 16.0f); dest = src + normal * 32.0f; // trace a line forward at duck height... @@ -2608,19 +2612,18 @@ bool Bot::canDuckUnder (const Vector &normal) { return tr.flFraction > 1.0f; } -#ifdef DEAD_CODE - bool Bot::isBlockedLeft () { TraceResult tr; - int direction = 48; + float direction = 48.0f; if (m_moveSpeed < 0.0f) { - direction = -48; + direction = -48.0f; } - makeVectors (pev->angles); + Vector right, forward; + pev->angles.buildVectors (&forward, &right, nullptr); // do a trace to the left... - game.TestLine (pev->origin, game.vec.forward * direction - game.vec.right * 48.0f, TraceIgnore::Monsters, ent (), &tr); + game.testLine (pev->origin, forward * direction - right * 48.0f, TraceIgnore::Monsters, ent (), &tr); // check if the trace hit something... if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0) { @@ -2631,15 +2634,16 @@ bool Bot::isBlockedLeft () { bool Bot::isBlockedRight () { TraceResult tr; - int direction = 48; + float direction = 48.0f; - if (m_moveSpeed < 0) { - direction = -48; + if (m_moveSpeed < 0.0f) { + direction = -48.0f; } - makeVectors (pev->angles); + Vector right, forward; + pev->angles.buildVectors (&forward, &right, nullptr); // do a trace to the right... - game.TestLine (pev->origin, pev->origin + game.vec.forward * direction + game.vec.right * 48.0f, TraceIgnore::Monsters, ent (), &tr); + game.testLine (pev->origin, pev->origin + forward * direction + right * 48.0f, TraceIgnore::Monsters, ent (), &tr); // check if the trace hit something... if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0)) { @@ -2648,13 +2652,9 @@ bool Bot::isBlockedRight () { return false; } -#endif - bool Bot::checkWallOnLeft () { TraceResult tr; - game.makeVectors (pev->angles); - - game.testLine (pev->origin, pev->origin - game.vec.right * 40.0f, TraceIgnore::Monsters, ent (), &tr); + game.testLine (pev->origin, pev->origin - pev->angles.right () * 40.0f, TraceIgnore::Monsters, ent (), &tr); // check if the trace hit something... if (tr.flFraction < 1.0f) { @@ -2665,10 +2665,9 @@ bool Bot::checkWallOnLeft () { bool Bot::checkWallOnRight () { TraceResult tr; - game.makeVectors (pev->angles); // do a trace to the right... - game.testLine (pev->origin, pev->origin + game.vec.right * 40.0f, TraceIgnore::Monsters, ent (), &tr); + game.testLine (pev->origin, pev->origin + pev->angles.right () * 40.0f, TraceIgnore::Monsters, ent (), &tr); // check if the trace hit something... if (tr.flFraction < 1.0f) { @@ -2680,17 +2679,11 @@ bool Bot::checkWallOnRight () { bool Bot::isDeadlyMove (const Vector &to) { // this function eturns if given location would hurt Bot with falling damage - Vector botPos = pev->origin; TraceResult tr; + const auto &direction = (to - pev->origin).normalize (); // 1 unit long - Vector move ((to - botPos).yaw (), 0.0f, 0.0f); - game.makeVectors (move); - - Vector direction = (to - botPos).normalize (); // 1 unit long - Vector check = botPos; - Vector down = botPos; - - down.z = down.z - 1000.0f; // straight down 1000 units + Vector check = to, down = to; + down.z -= 1000.0f; // straight down 1000 units game.testHull (check, down, TraceIgnore::Monsters, head_hull, ent (), &tr); @@ -2701,20 +2694,26 @@ bool Bot::isDeadlyMove (const Vector &to) { float lastHeight = tr.flFraction * 1000.0f; // height from ground float distance = (to - check).length (); // distance from goal - while (distance > 16.0f) { - check = check + direction * 16.0f; // move 10 units closer to the goal... + if (distance <= 30.0f && lastHeight > 150.0f) { + return true; + } + + while (distance > 30.0f) { + check = check + direction * 30.0f; // move 10 units closer to the goal... down = check; - down.z = down.z - 1000.0f; // straight down 1000 units + down.z -= 1000.0f; // straight down 1000 units game.testHull (check, down, TraceIgnore::Monsters, head_hull, ent (), &tr); - if (tr.fStartSolid) { // Wall blocking? + // wall blocking? + if (tr.fStartSolid) { return false; } float height = tr.flFraction * 1000.0f; // height from ground - if (lastHeight < height - 100.0f) {// Drops more than 100 Units? + // drops more than 150 units? + if (lastHeight < height - 150.0f) { return true; } lastHeight = height; @@ -2986,10 +2985,8 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) { } void Bot::setStrafeSpeed (const Vector &moveDir, float strafeSpeed) { - game.makeVectors (pev->angles); - const Vector &los = (moveDir - pev->origin).normalize2d (); - float dot = los | game.vec.forward.get2d (); + float dot = los | pev->angles.forward ().get2d (); if (dot > 0.0f && !checkWallOnRight ()) { m_strafeSpeed = strafeSpeed; @@ -3062,7 +3059,7 @@ edict_t *Bot::lookupButton (const char *targetName) { // this function tries to find nearest to current bot button, and returns pointer to // it's entity, also here must be specified the target, that button must open. - if (util.isEmptyStr (targetName)) { + if (strings.isEmpty (targetName)) { return nullptr; } float nearestDistance = kInfiniteDistance; @@ -3084,4 +3081,48 @@ edict_t *Bot::lookupButton (const char *targetName) { } } return foundEntity; -} \ No newline at end of file +} + + +bool Bot::isReachableNode (int index) { + // this function return whether bot able to reach index node or not, depending on several factors. + + if (!graph.exists (index)) { + return false; + } + const Vector &src = pev->origin; + const Vector &dst = graph[index].origin; + + // is the destination close enough? + if ((dst - src).lengthSq () >= cr::square (320.0f)) { + return false; + } + float ladderDist = (dst - src).length2d (); + + TraceResult tr; + game.testLine (src, dst, TraceIgnore::Monsters, ent (), &tr); + + // if node is visible from current position (even behind head)... + if (tr.flFraction >= 1.0f) { + + // it's should be not a problem to reach node inside water... + if (pev->waterlevel == 2 || pev->waterlevel == 3) { + return true; + } + + // check for ladder + bool nonLadder = !(graph[index].flags & NodeFlag::Ladder) || ladderDist > 16.0f; + + // is dest node higher than src? (62 is max jump height) + if (nonLadder && dst.z > src.z + 62.0f) { + return false; // can't reach this one + } + + // is dest node lower than src? + if (nonLadder && dst.z < src.z - 100.0f) { + return false; // can't reach this one + } + return true; + } + return false; +} diff --git a/source/support.cpp b/source/support.cpp index e518a53..aa2e9ad 100644 --- a/source/support.cpp +++ b/source/support.cpp @@ -67,10 +67,7 @@ bool BotUtils::isAlive (edict_t *ent) { } float BotUtils::getShootingCone (edict_t *ent, const Vector &position) { - game.makeVectors (ent->v.v_angle); - - // he's facing it, he meant it - return game.vec.forward | (position - (ent->v.origin + ent->v.view_ofs)).normalize (); + return ent->v.v_angle.forward () | (position - (ent->v.origin + ent->v.view_ofs)).normalize (); // he's facing it, he meant it } bool BotUtils::isInViewCone (const Vector &origin, edict_t *ent) { @@ -169,7 +166,7 @@ bool BotUtils::isPlayer (edict_t *ent) { } if ((ent->v.flags & (FL_CLIENT | FL_FAKECLIENT)) || bots[ent] != nullptr) { - return !isEmptyStr (STRING (ent->v.netname)); + return !strings.isEmpty (STRING (ent->v.netname)); } return false; } @@ -246,7 +243,7 @@ void BotUtils::checkWelcome () { game.serverCommand ("speak \"%s\"", m_sentences.random ().chars ()); } - MessageWriter (MSG_ONE, game.getMessageId (NetMsg::TextMsg), nullvec, receiveEntity) + MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullvec, receiveEntity) .writeByte (HUD_PRINTTALK) .writeString (strings.format ("----- %s v%s (Build: %u), {%s}, (c) %s, by %s (%s)-----", PRODUCT_SHORT_NAME, PRODUCT_VERSION, buildNumber (), PRODUCT_DATE, PRODUCT_END_YEAR, PRODUCT_AUTHOR, PRODUCT_URL)); @@ -319,7 +316,7 @@ void BotUtils::attachSoundsToClients (edict_t *ent, const char *sample, float vo // this function called by the sound hooking code (in emit_sound) enters the played sound into // the array associated with the entity - if (game.isNullEntity (ent) || isEmptyStr (sample)) { + if (game.isNullEntity (ent) || strings.isEmpty (sample)) { return; } const Vector &origin = game.getAbsPos (ent);