From 7d659fb8c2496296dc7c3bd4275edd5b15736b9f Mon Sep 17 00:00:00 2001 From: RGreenlees Date: Fri, 12 Apr 2024 16:40:50 +0100 Subject: [PATCH] Add cloak behaviour Bots will no longer see cloaked players, and have a chance based on cloak level, movement and size of the players. Bots will also sneak when approaching enemies while cloaked. --- main/source/detour/DetourNavMeshQuery.cpp | 13 +- main/source/detour/Include/DetourNavMesh.h | 138 +++---- .../detour/Include/DetourNavMeshQuery.h | 20 +- main/source/detour/Include/DetourNode.h | 26 +- main/source/mod/AvHAICommander.cpp | 372 +++++------------- main/source/mod/AvHAIMath.cpp | 2 + main/source/mod/AvHAINavigation.cpp | 271 ++++++++----- main/source/mod/AvHAINavigation.h | 1 + main/source/mod/AvHAIPlayer.cpp | 203 ++++++---- main/source/mod/AvHAIPlayer.h | 2 + main/source/mod/AvHAIPlayerManager.cpp | 123 ++++-- main/source/mod/AvHAIPlayerManager.h | 2 + main/source/mod/AvHAITask.cpp | 25 +- 13 files changed, 609 insertions(+), 589 deletions(-) diff --git a/main/source/detour/DetourNavMeshQuery.cpp b/main/source/detour/DetourNavMeshQuery.cpp index 03f06a09..d80b9916 100644 --- a/main/source/detour/DetourNavMeshQuery.cpp +++ b/main/source/detour/DetourNavMeshQuery.cpp @@ -1130,6 +1130,9 @@ dtStatus dtNavMeshQuery::findPath(dtPolyRef startRef, dtPolyRef endRef, { // Remove node from open list and put it in closed list. dtNode* bestNode = m_openList->pop(); + + if (!bestNode) { continue; } + bestNode->flags &= ~DT_NODE_OPEN; bestNode->flags |= DT_NODE_CLOSED; @@ -1143,14 +1146,16 @@ dtStatus dtNavMeshQuery::findPath(dtPolyRef startRef, dtPolyRef endRef, // Get current poly and tile. // The API input has been cheked already, skip checking internal data. const dtPolyRef bestRef = bestNode->id; - const dtMeshTile* bestTile = 0; - const dtPoly* bestPoly = 0; + const dtMeshTile* bestTile = nullptr; + const dtPoly* bestPoly = nullptr; m_nav->getTileAndPolyByRefUnsafe(bestRef, &bestTile, &bestPoly); + + if (!bestTile || !bestPoly || !bestTile->links) { continue; } // Get parent poly and tile. dtPolyRef parentRef = 0; - const dtMeshTile* parentTile = 0; - const dtPoly* parentPoly = 0; + const dtMeshTile* parentTile = nullptr; + const dtPoly* parentPoly = nullptr; if (bestNode->pidx) parentRef = m_nodePool->getNodeAtIdx(bestNode->pidx)->id; if (parentRef) diff --git a/main/source/detour/Include/DetourNavMesh.h b/main/source/detour/Include/DetourNavMesh.h index 312ef4e3..d78f8a79 100644 --- a/main/source/detour/Include/DetourNavMesh.h +++ b/main/source/detour/Include/DetourNavMesh.h @@ -163,7 +163,7 @@ enum OffMeshState struct dtPoly { /// Index to first link in linked list. (Or #DT_NULL_LINK if there is no link.) - unsigned int firstLink; + unsigned int firstLink = DT_NULL_LINK; /// The indices of the polygon's vertices. /// The actual vertices are located in dtMeshTile::verts. @@ -173,14 +173,14 @@ struct dtPoly unsigned short neis[DT_VERTS_PER_POLYGON]; /// The user defined polygon flags. - unsigned int flags; + unsigned int flags = 0; /// The number of vertices in the polygon. - unsigned char vertCount; + unsigned char vertCount = 0; /// The bit packed area id and polygon type. /// @note Use the structure's set and get methods to acess this value. - unsigned char areaAndtype; + unsigned char areaAndtype = 0; /// Sets the user defined area id. [Limit: < #DT_MAX_AREAS] inline void setArea(unsigned char a) { areaAndtype = (areaAndtype & 0xc0) | (a & 0x3f); } @@ -200,10 +200,10 @@ struct dtPoly /// Defines the location of detail sub-mesh data within a dtMeshTile. struct dtPolyDetail { - unsigned int vertBase; ///< The offset of the vertices in the dtMeshTile::detailVerts array. - unsigned int triBase; ///< The offset of the triangles in the dtMeshTile::detailTris array. - unsigned char vertCount; ///< The number of vertices in the sub-mesh. - unsigned char triCount; ///< The number of triangles in the sub-mesh. + unsigned int vertBase = 0; ///< The offset of the vertices in the dtMeshTile::detailVerts array. + unsigned int triBase = 0; ///< The offset of the triangles in the dtMeshTile::detailTris array. + unsigned char vertCount = 0; ///< The number of vertices in the sub-mesh. + unsigned char triCount = 0; ///< The number of triangles in the sub-mesh. }; /// Defines a link between polygons. @@ -211,12 +211,12 @@ struct dtPolyDetail /// @see dtMeshTile struct dtLink { - dtPolyRef ref; ///< Neighbour reference. (The neighbor that is linked to.) - unsigned int next; ///< Index of the next link. - unsigned char edge; ///< Index of the polygon edge that owns this link. - unsigned char side; ///< If a boundary link, defines on which side the link is. - unsigned char bmin; ///< If a boundary link, defines the minimum sub-edge area. - unsigned char bmax; ///< If a boundary link, defines the maximum sub-edge area. + dtPolyRef ref = 0; ///< Neighbour reference. (The neighbor that is linked to.) + unsigned int next = 0; ///< Index of the next link. + unsigned char edge = 0; ///< Index of the polygon edge that owns this link. + unsigned char side = 0; ///< If a boundary link, defines on which side the link is. + unsigned char bmin = 0; ///< If a boundary link, defines the minimum sub-edge area. + unsigned char bmax = 0; ///< If a boundary link, defines the maximum sub-edge area. int OffMeshID = -1; ///< If an off-mesh connection, this will be the UserID of the connection that made this link }; @@ -225,8 +225,8 @@ struct dtLink /// @see dtMeshTile struct dtBVNode { - unsigned short bmin[3]; ///< Minimum bounds of the node's AABB. [(x, y, z)] - unsigned short bmax[3]; ///< Maximum bounds of the node's AABB. [(x, y, z)] + unsigned short bmin[3] = { 0 }; ///< Minimum bounds of the node's AABB. [(x, y, z)] + unsigned short bmax[3] = { 0 }; ///< Maximum bounds of the node's AABB. [(x, y, z)] int i; ///< The node's index. (Negative for escape sequence.) }; @@ -235,7 +235,7 @@ struct dtBVNode struct dtOffMeshConnection { /// The endpoints of the connection. [(ax, ay, az, bx, by, bz)] - float pos[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; + float pos[6] = { 0.0f }; /// The radius of the endpoints. [Limit: >= 0] float rad = 0.0f; @@ -280,65 +280,65 @@ struct dtOffMeshConnection /// @ingroup detour struct dtMeshHeader { - int magic; ///< Tile magic number. (Used to identify the data format.) - int version; ///< Tile data format version number. - int x; ///< The x-position of the tile within the dtNavMesh tile grid. (x, y, layer) - int y; ///< The y-position of the tile within the dtNavMesh tile grid. (x, y, layer) - int layer; ///< The layer of the tile within the dtNavMesh tile grid. (x, y, layer) - unsigned int userId; ///< The user defined id of the tile. - int polyCount; ///< The number of polygons in the tile. - int vertCount; ///< The number of vertices in the tile. - int maxLinkCount; ///< The number of allocated links. - int detailMeshCount; ///< The number of sub-meshes in the detail mesh. + int magic = 0; ///< Tile magic number. (Used to identify the data format.) + int version = 0; ///< Tile data format version number. + int x = 0; ///< The x-position of the tile within the dtNavMesh tile grid. (x, y, layer) + int y = 0; ///< The y-position of the tile within the dtNavMesh tile grid. (x, y, layer) + int layer = 0; ///< The layer of the tile within the dtNavMesh tile grid. (x, y, layer) + unsigned int userId = 0; ///< The user defined id of the tile. + int polyCount = 0; ///< The number of polygons in the tile. + int vertCount = 0; ///< The number of vertices in the tile. + int maxLinkCount = 0; ///< The number of allocated links. + int detailMeshCount = 0; ///< The number of sub-meshes in the detail mesh. /// The number of unique vertices in the detail mesh. (In addition to the polygon vertices.) - int detailVertCount; + int detailVertCount = 0; - int detailTriCount; ///< The number of triangles in the detail mesh. - int bvNodeCount; ///< The number of bounding volume nodes. (Zero if bounding volumes are disabled.) - int offMeshConCount; ///< The number of off-mesh connections. + int detailTriCount = 0; ///< The number of triangles in the detail mesh. + int bvNodeCount = 0; ///< The number of bounding volume nodes. (Zero if bounding volumes are disabled.) + int offMeshConCount = 0; ///< The number of off-mesh connections. - int offMeshBase; ///< The index of the first polygon which is an off-mesh connection. - float walkableHeight; ///< The height of the agents using the tile. - float walkableRadius; ///< The radius of the agents using the tile. - float walkableClimb; ///< The maximum climb height of the agents using the tile. - float bmin[3]; ///< The minimum bounds of the tile's AABB. [(x, y, z)] - float bmax[3]; ///< The maximum bounds of the tile's AABB. [(x, y, z)] + int offMeshBase = 0; ///< The index of the first polygon which is an off-mesh connection. + float walkableHeight = 0.0f; ///< The height of the agents using the tile. + float walkableRadius = 0.0f; ///< The radius of the agents using the tile. + float walkableClimb = 0.0f; ///< The maximum climb height of the agents using the tile. + float bmin[3] = { 0.0f }; ///< The minimum bounds of the tile's AABB. [(x, y, z)] + float bmax[3] = { 0.0f }; ///< The maximum bounds of the tile's AABB. [(x, y, z)] /// The bounding volume quantization factor. - float bvQuantFactor; + float bvQuantFactor = 0.0f; }; /// Defines a navigation mesh tile. /// @ingroup detour struct dtMeshTile { - unsigned int salt; ///< Counter describing modifications to the tile. + unsigned int salt = 0; ///< Counter describing modifications to the tile. - unsigned int linksFreeList; ///< Index to the next free link. - dtMeshHeader* header; ///< The tile header. - dtPoly* polys; ///< The tile polygons. [Size: dtMeshHeader::polyCount] - float* verts; ///< The tile vertices. [Size: dtMeshHeader::vertCount] - dtLink* links; ///< The tile links. [Size: dtMeshHeader::maxLinkCount] - dtPolyDetail* detailMeshes; ///< The tile's detail sub-meshes. [Size: dtMeshHeader::detailMeshCount] + unsigned int linksFreeList = 0; ///< Index to the next free link. + dtMeshHeader* header = nullptr; ///< The tile header. + dtPoly* polys = nullptr; ///< The tile polygons. [Size: dtMeshHeader::polyCount] + float* verts = nullptr; ///< The tile vertices. [Size: dtMeshHeader::vertCount] + dtLink* links = nullptr; ///< The tile links. [Size: dtMeshHeader::maxLinkCount] + dtPolyDetail* detailMeshes = nullptr; ///< The tile's detail sub-meshes. [Size: dtMeshHeader::detailMeshCount] /// The detail mesh's unique vertices. [(x, y, z) * dtMeshHeader::detailVertCount] - float* detailVerts; + float* detailVerts = nullptr; /// The detail mesh's triangles. [(vertA, vertB, vertC, triFlags) * dtMeshHeader::detailTriCount]. /// See dtDetailTriEdgeFlags and dtGetDetailTriEdgeFlags. - unsigned char* detailTris; + unsigned char* detailTris = nullptr; /// The tile bounding volume nodes. [Size: dtMeshHeader::bvNodeCount] /// (Will be null if bounding volumes are disabled.) - dtBVNode* bvTree; + dtBVNode* bvTree = nullptr; - dtOffMeshConnection** offMeshCons; ///< The tile off-mesh connections. [Size: dtMeshHeader::offMeshConCount] + dtOffMeshConnection** offMeshCons = nullptr; ///< The tile off-mesh connections. [Size: dtMeshHeader::offMeshConCount] - unsigned char* data; ///< The tile data. (Not directly accessed under normal situations.) - int dataSize; ///< Size of the tile data. - int flags; ///< Tile flags. (See: #dtTileFlags) - dtMeshTile* next; ///< The next free tile, or the next tile in the spatial grid. + unsigned char* data = nullptr; ///< The tile data. (Not directly accessed under normal situations.) + int dataSize = 0; ///< Size of the tile data. + int flags = 0; ///< Tile flags. (See: #dtTileFlags) + dtMeshTile* next = nullptr; ///< The next free tile, or the next tile in the spatial grid. private: dtMeshTile(const dtMeshTile&); dtMeshTile& operator=(const dtMeshTile&); @@ -359,11 +359,11 @@ inline int dtGetDetailTriEdgeFlags(unsigned char triFlags, int edgeIndex) /// @ingroup detour struct dtNavMeshParams { - float orig[3]; ///< The world space origin of the navigation mesh's tile space. [(x, y, z)] - float tileWidth; ///< The width of each tile. (Along the x-axis.) - float tileHeight; ///< The height of each tile. (Along the z-axis.) - int maxTiles; ///< The maximum number of tiles the navigation mesh can contain. This and maxPolys are used to calculate how many bits are needed to identify tiles and polygons uniquely. - int maxPolys; ///< The maximum number of polygons each tile can contain. This and maxTiles are used to calculate how many bits are needed to identify tiles and polygons uniquely. + float orig[3] = { 0.0f }; ///< The world space origin of the navigation mesh's tile space. [(x, y, z)] + float tileWidth = 0.0f; ///< The width of each tile. (Along the x-axis.) + float tileHeight = 0.0f; ///< The height of each tile. (Along the z-axis.) + int maxTiles = 0; ///< The maximum number of tiles the navigation mesh can contain. This and maxPolys are used to calculate how many bits are needed to identify tiles and polygons uniquely. + int maxPolys = 0; ///< The maximum number of polygons each tile can contain. This and maxTiles are used to calculate how many bits are needed to identify tiles and polygons uniquely. }; /// A navigation mesh based on tiles of convex polygons. @@ -700,20 +700,20 @@ private: void closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const; dtNavMeshParams m_params; ///< Current initialization params. TODO: do not store this info twice. - float m_orig[3]; ///< Origin of the tile (0,0) - float m_tileWidth, m_tileHeight; ///< Dimensions of each tile. - int m_maxTiles; ///< Max number of tiles. - int m_tileLutSize; ///< Tile hash lookup size (must be pot). - int m_tileLutMask; ///< Tile hash lookup mask. + float m_orig[3] = { 0.0f }; ///< Origin of the tile (0,0) + float m_tileWidth = 0.0f, m_tileHeight = 0.0f; ///< Dimensions of each tile. + int m_maxTiles = 0; ///< Max number of tiles. + int m_tileLutSize = 0; ///< Tile hash lookup size (must be pot). + int m_tileLutMask = 0; ///< Tile hash lookup mask. - dtMeshTile** m_posLookup; ///< Tile hash lookup. - dtMeshTile* m_nextFree; ///< Freelist of tiles. - dtMeshTile* m_tiles; ///< List of tiles. + dtMeshTile** m_posLookup = nullptr; ///< Tile hash lookup. + dtMeshTile* m_nextFree = nullptr; ///< Freelist of tiles. + dtMeshTile* m_tiles = nullptr; ///< List of tiles. #ifndef DT_POLYREF64 - unsigned int m_saltBits; ///< Number of salt bits in the tile ID. - unsigned int m_tileBits; ///< Number of tile bits in the tile ID. - unsigned int m_polyBits; ///< Number of poly bits in the tile ID. + unsigned int m_saltBits = 0; ///< Number of salt bits in the tile ID. + unsigned int m_tileBits = 0; ///< Number of tile bits in the tile ID. + unsigned int m_polyBits = 0; ///< Number of poly bits in the tile ID. #endif friend class dtNavMeshQuery; diff --git a/main/source/detour/Include/DetourNavMeshQuery.h b/main/source/detour/Include/DetourNavMeshQuery.h index e43dd3f4..57ba6538 100644 --- a/main/source/detour/Include/DetourNavMeshQuery.h +++ b/main/source/detour/Include/DetourNavMeshQuery.h @@ -34,9 +34,9 @@ /// @ingroup detour class dtQueryFilter { - float m_areaCost[DT_MAX_AREAS]; ///< Cost per area type. (Used by default implementation.) - unsigned int m_includeFlags; ///< Flags for polygons that can be visited. (Used by default implementation.) - unsigned int m_excludeFlags; ///< Flags for polygons that should not be visted. (Used by default implementation.) + float m_areaCost[DT_MAX_AREAS] = { 1.0f }; ///< Cost per area type. (Used by default implementation.) + unsigned int m_includeFlags = 0; ///< Flags for polygons that can be visited. (Used by default implementation.) + unsigned int m_excludeFlags = 0; ///< Flags for polygons that should not be visted. (Used by default implementation.) public: dtQueryFilter(); @@ -130,25 +130,25 @@ public: struct dtRaycastHit { /// The hit parameter. (FLT_MAX if no wall hit.) - float t; + float t = 0.0f; /// hitNormal The normal of the nearest wall hit. [(x, y, z)] - float hitNormal[3]; + float hitNormal[3] = { 0.0f }; /// The index of the edge on the final polygon where the wall was hit. - int hitEdgeIndex; + int hitEdgeIndex = 0; /// Pointer to an array of reference ids of the visited polygons. [opt] - dtPolyRef* path; + dtPolyRef* path = nullptr; /// The number of visited polygons. [opt] - int pathCount; + int pathCount = 0; /// The maximum number of polygons the @p path array can hold. - int maxPath; + int maxPath = 0; /// The cost of the path until hit. - float pathCost; + float pathCost = 0; }; /// Provides custom polygon query behavior. diff --git a/main/source/detour/Include/DetourNode.h b/main/source/detour/Include/DetourNode.h index db097470..7bcbeeb1 100644 --- a/main/source/detour/Include/DetourNode.h +++ b/main/source/detour/Include/DetourNode.h @@ -35,13 +35,13 @@ static const int DT_NODE_PARENT_BITS = 24; static const int DT_NODE_STATE_BITS = 2; struct dtNode { - float pos[3]; ///< Position of the node. - float cost; ///< Cost from previous node to current node. - float total; ///< Cost up to the node. + float pos[3] = { 0.0f }; ///< Position of the node. + float cost = 0.0f; ///< Cost from previous node to current node. + float total = 0.0f; ///< Cost up to the node. unsigned int pidx : DT_NODE_PARENT_BITS; ///< Index to parent node. unsigned int state : DT_NODE_STATE_BITS; ///< extra state information. A polyRef can have multiple nodes with different extra info. see DT_MAX_STATES_PER_NODE unsigned int flags : 3; ///< Node flags. A combination of dtNodeFlags. - dtPolyRef id; ///< Polygon ref the node corresponds to. + dtPolyRef id = 0; ///< Polygon ref the node corresponds to. }; static const int DT_MAX_STATES_PER_NODE = 1 << DT_NODE_STATE_BITS; // number of extra states per node. See dtNode::state @@ -97,12 +97,12 @@ private: dtNodePool(const dtNodePool&); dtNodePool& operator=(const dtNodePool&); - dtNode* m_nodes; - dtNodeIndex* m_first; - dtNodeIndex* m_next; - const int m_maxNodes; - const int m_hashSize; - int m_nodeCount; + dtNode* m_nodes = nullptr; + dtNodeIndex* m_first = nullptr; + dtNodeIndex* m_next = nullptr; + const int m_maxNodes = 0; + const int m_hashSize = 0; + int m_nodeCount = 0; }; class dtNodeQueue @@ -159,9 +159,9 @@ private: void bubbleUp(int i, dtNode* node); void trickleDown(int i, dtNode* node); - dtNode** m_heap; - const int m_capacity; - int m_size; + dtNode** m_heap = nullptr; + const int m_capacity = 0; + int m_size = 0; }; diff --git a/main/source/mod/AvHAICommander.cpp b/main/source/mod/AvHAICommander.cpp index b6d9ead9..dabcf8d4 100644 --- a/main/source/mod/AvHAICommander.cpp +++ b/main/source/mod/AvHAICommander.cpp @@ -145,7 +145,7 @@ bool AICOMM_UpgradeStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* Structu bool AICOMM_RecycleStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToRecycle) { - if (!StructureToRecycle || StructureToRecycle->StructureType == STRUCTURE_MARINE_DEPLOYEDMINE) { return false; } + if (!StructureToRecycle || StructureToRecycle->StructureType == STRUCTURE_MARINE_DEPLOYEDMINE || UTIL_StructureIsRecycling(StructureToRecycle->edict)) { return false; } return AICOMM_ResearchTech(pBot, StructureToRecycle, BUILD_RECYCLE); } @@ -956,11 +956,11 @@ bool AICOMM_IsRequestValid(ai_commander_request* Request) return !IsPlayerBuffed(Requestor) && !AITAC_ItemExistsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_CATALYSTS, RequestorTeam, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); case BUILD_PHASEGATE: - return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, STRUCTURE_MARINE_PHASEGATE, Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, STRUCTURE_MARINE_PHASEGATE, Requestor->v.origin, UTIL_MetresToGoldSrcUnits(10.0f)); case BUILD_TURRET_FACTORY: - return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(15.0f)); case BUILD_ARMORY: - return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(15.0f)); case BUILD_COMMANDSTATION: return !AITAC_IsStructureOfTypeNearLocation(RequestorTeam, STRUCTURE_MARINE_COMMCHAIR, Requestor->v.origin, UTIL_MetresToGoldSrcUnits(10.0f)); case BUILD_SCAN: @@ -2927,305 +2927,124 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) } - if (NextRequest->RequestType == BUILD_PHASEGATE) + + if (NextRequest->RequestType == BUILD_PHASEGATE && !AITAC_ResearchIsComplete(CommanderTeam, TECH_RESEARCH_PHASETECH)) { - if (!AITAC_ResearchIsComplete(CommanderTeam, TECH_RESEARCH_PHASETECH)) - { - char msg[128]; - sprintf(msg, "We haven't got phase tech yet, %s. Ask again later.", STRING(Requestor->v.netname)); - BotSay(pBot, true, 0.5f, msg); - NextRequest->bResponded = true; - return false; - } - - if (pBot->Player->GetResources() < BALANCE_VAR(kPhaseGateCost)) - { - if (!NextRequest->bAcknowledged) - { - char msg[128]; - sprintf(msg, "Just waiting on resources, %s. Will drop asap.", STRING(Requestor->v.netname)); - BotSay(pBot, true, 0.5f, msg); - NextRequest->bAcknowledged = true; - return false; - } - return false; - } - - Vector IdealDeployLocation = Requestor->v.origin + (UTIL_GetForwardVector2D(Requestor->v.angles) * 75.0f); - Vector ProjectedDeployLocation = AdjustPointForPathfinding(IdealDeployLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); - - if (!vIsZero(ProjectedDeployLocation)) - { - bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, ProjectedDeployLocation, STRUCTURE_PURPOSE_NONE); - - if (bSuccess) - { - NextRequest->bResponded = true; - return true; - } - } - - Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); - - if (!vIsZero(DeployLocation)) - { - bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, DeployLocation, STRUCTURE_PURPOSE_NONE); - - if (bSuccess) - { - NextRequest->bResponded = true; - return true; - } - } - - DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); - - if (!vIsZero(DeployLocation)) - { - bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, DeployLocation, STRUCTURE_PURPOSE_NONE); - - if (bSuccess) - { - NextRequest->bResponded = true; - return true; - } - else - { - char msg[128]; - sprintf(msg, "I can't find a good deploy spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); - BotSay(pBot, true, 0.5f, msg); - NextRequest->bResponded = true; - return false; - } - } - else - { - char msg[128]; - sprintf(msg, "I can't find a good deploy spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); - BotSay(pBot, true, 0.5f, msg); - NextRequest->bResponded = true; - return false; - } - + char msg[128]; + sprintf(msg, "We haven't got phase tech yet, %s. Ask again later.", STRING(Requestor->v.netname)); + BotSay(pBot, true, 0.5f, msg); + NextRequest->bResponded = true; return false; - } - if (NextRequest->RequestType == BUILD_ARMORY || NextRequest->RequestType == BUILD_COMMANDSTATION) + if (NextRequest->RequestType == BUILD_OBSERVATORY && !AITAC_ResearchIsComplete(CommanderTeam, TECH_RESEARCH_PHASETECH)) { - float RequiredRes = (NextRequest->RequestType == BUILD_ARMORY) ? BALANCE_VAR(kArmoryCost) : BALANCE_VAR(kCommandStationCost); + DeployableSearchFilter ArmouryFilter; + ArmouryFilter.DeployableTeam = CommanderTeam; + ArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); + ArmouryFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + ArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; - if (pBot->Player->GetResources() < RequiredRes) - { - if (!NextRequest->bAcknowledged) - { - char msg[128]; - sprintf(msg, "Just waiting on resources, %s. Will drop asap.", STRING(Requestor->v.netname)); - BotSay(pBot, true, 0.5f, msg); - NextRequest->bAcknowledged = true; - return false; - } - return false; - } + bool bHasArmoury = AITAC_DeployableExistsAtLocation(ZERO_VECTOR, &ArmouryFilter); - Vector IdealDeployLocation = Requestor->v.origin + (UTIL_GetForwardVector2D(Requestor->v.angles) * 75.0f); - Vector ProjectedDeployLocation = AdjustPointForPathfinding(IdealDeployLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); - - AvHAIDeployableStructureType StructureToDeploy = (NextRequest->RequestType == BUILD_ARMORY) ? STRUCTURE_MARINE_ARMOURY : STRUCTURE_MARINE_COMMCHAIR; - - if (!vIsZero(ProjectedDeployLocation)) - { - bool bSuccess = AICOMM_DeployStructure(pBot, StructureToDeploy, ProjectedDeployLocation, STRUCTURE_PURPOSE_NONE); - - if (bSuccess) - { - NextRequest->bResponded = true; - return true; - } - } - - Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); - - if (!vIsZero(DeployLocation)) - { - bool bSuccess = AICOMM_DeployStructure(pBot, StructureToDeploy, DeployLocation, STRUCTURE_PURPOSE_NONE); - - if (bSuccess) - { - NextRequest->bResponded = true; - return true; - } - } - - DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); - - if (!vIsZero(DeployLocation)) - { - bool bSuccess = AICOMM_DeployStructure(pBot, StructureToDeploy, DeployLocation, STRUCTURE_PURPOSE_NONE); - - if (bSuccess) - { - NextRequest->bResponded = true; - return true; - } - else - { - char msg[128]; - sprintf(msg, "I can't find a good deploy spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); - BotSay(pBot, true, 0.5f, msg); - NextRequest->bResponded = true; - return false; - } - } - else + if (!bHasArmoury) { char msg[128]; - sprintf(msg, "I can't find a good deploy spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); + sprintf(msg, "We haven't got an armory yet, %s. Ask again later.", STRING(Requestor->v.netname)); BotSay(pBot, true, 0.5f, msg); NextRequest->bResponded = true; return false; } - - return false; - } - if (NextRequest->RequestType == BUILD_TURRET_FACTORY) + + float RequiredRes = 0.0f; + AvHAIDeployableStructureType StructureToDeploy = STRUCTURE_NONE; + + switch (NextRequest->RequestType) { - if (pBot->Player->GetResources() < BALANCE_VAR(kTurretFactoryCost)) - { - if (!NextRequest->bAcknowledged) - { - char msg[128]; - sprintf(msg, "Just waiting on resources, %s. Will drop asap.", STRING(Requestor->v.netname)); - BotSay(pBot, true, 0.5f, msg); - NextRequest->bAcknowledged = true; - return false; - } - return false; - } - - Vector IdealDeployLocation = Requestor->v.origin + (UTIL_GetForwardVector2D(Requestor->v.angles) * 75.0f); - Vector ProjectedDeployLocation = AdjustPointForPathfinding(IdealDeployLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); - - if (!vIsZero(ProjectedDeployLocation)) - { - bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, ProjectedDeployLocation, STRUCTURE_PURPOSE_NONE); - - if (bSuccess) - { - NextRequest->bResponded = true; - return true; - } - } - - Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); - - if (!vIsZero(DeployLocation)) - { - bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, DeployLocation, STRUCTURE_PURPOSE_NONE); - - if (bSuccess) - { - NextRequest->bResponded = true; - return true; - } - } - - DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); - - if (!vIsZero(DeployLocation)) - { - bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, DeployLocation, STRUCTURE_PURPOSE_NONE); - - if (bSuccess) - { - NextRequest->bResponded = true; - return true; - } - else - { - char msg[128]; - sprintf(msg, "I can't find a good deploy spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); - BotSay(pBot, true, 0.5f, msg); - NextRequest->bResponded = true; - return false; - } - } - else - { - char msg[128]; - sprintf(msg, "I can't find a good deploy spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); - BotSay(pBot, true, 0.5f, msg); - NextRequest->bResponded = true; - return false; - } - - return false; - + case BUILD_ARMORY: + RequiredRes = BALANCE_VAR(kArmoryCost); + StructureToDeploy = STRUCTURE_MARINE_ARMOURY; + break; + case BUILD_COMMANDSTATION: + RequiredRes = BALANCE_VAR(kCommandStationCost); + StructureToDeploy = STRUCTURE_MARINE_COMMCHAIR; + break; + case BUILD_OBSERVATORY: + RequiredRes = BALANCE_VAR(kObservatoryCost); + StructureToDeploy = STRUCTURE_MARINE_OBSERVATORY; + break; + case BUILD_TURRET_FACTORY: + RequiredRes = BALANCE_VAR(kTurretFactoryCost); + StructureToDeploy = STRUCTURE_MARINE_TURRETFACTORY; + case BUILD_TURRET: + RequiredRes = BALANCE_VAR(kSentryCost); + StructureToDeploy = STRUCTURE_MARINE_TURRET; + case BUILD_PHASEGATE: + RequiredRes = BALANCE_VAR(kPhaseGateCost); + StructureToDeploy = STRUCTURE_MARINE_PHASEGATE; + default: + break; } - if (NextRequest->RequestType == BUILD_TURRET) + // Invalid commander request + if (StructureToDeploy == STRUCTURE_NONE) { - if (pBot->Player->GetResources() < BALANCE_VAR(kSentryCost)) + NextRequest->bResponded = true; + return false; + } + + if (pBot->Player->GetResources() < RequiredRes) + { + if (!NextRequest->bAcknowledged) { - if (!NextRequest->bAcknowledged) - { - char msg[128]; - sprintf(msg, "Just waiting on resources, %s. Will drop asap.", STRING(Requestor->v.netname)); - BotSay(pBot, true, 0.5f, msg); - NextRequest->bAcknowledged = true; - return false; - } + char msg[128]; + sprintf(msg, "Just waiting on resources, %s. Will drop asap.", STRING(Requestor->v.netname)); + BotSay(pBot, true, 0.5f, msg); + NextRequest->bAcknowledged = true; return false; } + return false; + } - Vector IdealDeployLocation = Requestor->v.origin + (UTIL_GetForwardVector2D(Requestor->v.angles) * 75.0f); - Vector ProjectedDeployLocation = AdjustPointForPathfinding(IdealDeployLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); + Vector IdealDeployLocation = Requestor->v.origin + (UTIL_GetForwardVector2D(Requestor->v.angles) * 75.0f); + Vector ProjectedDeployLocation = AdjustPointForPathfinding(IdealDeployLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); - if (!vIsZero(ProjectedDeployLocation)) + if (!vIsZero(ProjectedDeployLocation)) + { + bool bSuccess = AICOMM_DeployStructure(pBot, StructureToDeploy, ProjectedDeployLocation, STRUCTURE_PURPOSE_NONE); + + if (bSuccess) { - bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRET, ProjectedDeployLocation, STRUCTURE_PURPOSE_NONE); - - if (bSuccess) - { - NextRequest->bResponded = true; - return true; - } + NextRequest->bResponded = true; + return true; } + } - Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); - if (!vIsZero(DeployLocation)) + if (!vIsZero(DeployLocation)) + { + bool bSuccess = AICOMM_DeployStructure(pBot, StructureToDeploy, DeployLocation, STRUCTURE_PURPOSE_NONE); + + if (bSuccess) { - bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRET, DeployLocation, STRUCTURE_PURPOSE_NONE); - - if (bSuccess) - { - NextRequest->bResponded = true; - return true; - } + NextRequest->bResponded = true; + return true; } + } - DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + DeployLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); - if (!vIsZero(DeployLocation)) + if (!vIsZero(DeployLocation)) + { + bool bSuccess = AICOMM_DeployStructure(pBot, StructureToDeploy, DeployLocation, STRUCTURE_PURPOSE_NONE); + + if (bSuccess) { - bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRET, DeployLocation, STRUCTURE_PURPOSE_NONE); - - if (bSuccess) - { - NextRequest->bResponded = true; - return true; - } - else - { - char msg[128]; - sprintf(msg, "I can't find a good deploy spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); - BotSay(pBot, true, 0.5f, msg); - NextRequest->bResponded = true; - return false; - } + NextRequest->bResponded = true; + return true; } else { @@ -3235,9 +3054,14 @@ bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) NextRequest->bResponded = true; return false; } - + } + else + { + char msg[128]; + sprintf(msg, "I can't find a good deploy spot, %s. Try again elsewhere.", STRING(Requestor->v.netname)); + BotSay(pBot, true, 0.5f, msg); + NextRequest->bResponded = true; return false; - } return false; @@ -3679,6 +3503,10 @@ void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const { NewRequestType = BUILD_COMMANDSTATION; } + else if (!stricmp(Request, "obs") || !stricmp(Request, "observatory")) + { + NewRequestType = BUILD_OBSERVATORY; + } if (NewRequestType == MESSAGE_NULL) { return; } diff --git a/main/source/mod/AvHAIMath.cpp b/main/source/mod/AvHAIMath.cpp index f994a361..13d823b4 100644 --- a/main/source/mod/AvHAIMath.cpp +++ b/main/source/mod/AvHAIMath.cpp @@ -815,6 +815,8 @@ float UTIL_CalculateSlopeAngleBetweenPoints(const Vector StartPoint, const Vecto // Function to check if a finite line intersects with an AABB bool vlineIntersectsAABB(Vector lineStart, Vector lineEnd, Vector BoxMinPosition, Vector BoxMaxPosition) { + if (vPointOverlaps3D(lineStart, BoxMinPosition, BoxMaxPosition) || vPointOverlaps3D(lineEnd, BoxMinPosition, BoxMaxPosition)) { return true; } + Vector RayDir = UTIL_GetVectorNormal(lineEnd - lineStart); float LineLength = vDist3D(lineStart, lineEnd); Vector dirfrac; diff --git a/main/source/mod/AvHAINavigation.cpp b/main/source/mod/AvHAINavigation.cpp index 3827e949..3b55d255 100644 --- a/main/source/mod/AvHAINavigation.cpp +++ b/main/source/mod/AvHAINavigation.cpp @@ -2522,6 +2522,27 @@ void CheckAndHandleDoorObstruction(AvHAIPlayer* pBot) DoorTrigger* Trigger = UTIL_GetNearestDoorTrigger(pBot->CurrentFloorPosition, Door, nullptr, true); + // Fail-safe: If the bot cannot reach any trigger for whatever reason, then telepathically trigger one otherwise it will be stuck forever + if (!Trigger) + { + for (auto it = Door->TriggerEnts.begin(); it != Door->TriggerEnts.end(); it++) + { + if (it->NextActivationTime > gpGlobals->time) + { + Trigger = nullptr; + break; + } + + Trigger = &(*it); + } + + if (Trigger) + { + Trigger->Entity->Use(pBot->Player, pBot->Player, USE_TOGGLE, 0.0f); + return; + } + } + if (Trigger && Trigger->NextActivationTime < gpGlobals->time) { if (Trigger->TriggerType == DOOR_BUTTON) @@ -2562,45 +2583,56 @@ edict_t* UTIL_GetDoorBlockingPathPoint(AvHAIPlayer* pBot, bot_path_node* PathNod { Vector TargetLoc = Vector(FromLoc.x, FromLoc.y, PathNode->requiredZ); - UTIL_TraceLine(FromLoc, TargetLoc, ignore_monsters, dont_ignore_glass, (pBot!= nullptr) ? pBot->Edict->v.pContainingEntity : nullptr, &doorHit); - if (!FNullEnt(SearchDoor)) { - if (doorHit.pHit == SearchDoor) { return doorHit.pHit; } + if (vlineIntersectsAABB(FromLoc, TargetLoc, SearchDoor->v.absmin, SearchDoor->v.absmax)) + { + return SearchDoor; + } } else { - if (!FNullEnt(doorHit.pHit)) + for (auto it = NavDoors.begin(); it != NavDoors.end(); it++) { - if (strcmp(STRING(doorHit.pHit->v.classname), "func_door") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_seethroughdoor") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_door_rotating") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "avhweldable") == 0) + if (vlineIntersectsAABB(FromLoc, TargetLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax)) { - return doorHit.pHit; + return it->DoorEdict; + } + } + + for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++) + { + if (vlineIntersectsAABB(FromLoc, TargetLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax)) + { + return it->WeldableEdict; } } - } Vector TargetLoc2 = Vector(ToLoc.x, ToLoc.y, PathNode->requiredZ); - UTIL_TraceLine(TargetLoc, TargetLoc2, ignore_monsters, dont_ignore_glass, (pBot != nullptr) ? pBot->Edict->v.pContainingEntity : nullptr, &doorHit); - if (!FNullEnt(SearchDoor)) { - if (doorHit.pHit == SearchDoor) { return doorHit.pHit; } + if (vlineIntersectsAABB(TargetLoc, TargetLoc2, SearchDoor->v.absmin, SearchDoor->v.absmax)) + { + return SearchDoor; + } } else { - if (!FNullEnt(doorHit.pHit)) + for (auto it = NavDoors.begin(); it != NavDoors.end(); it++) { - if (strcmp(STRING(doorHit.pHit->v.classname), "func_door") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_seethroughdoor") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_door_rotating") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "avhweldable") == 0) + if (vlineIntersectsAABB(TargetLoc, TargetLoc2, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax)) { - return doorHit.pHit; + return it->DoorEdict; + } + } + + for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++) + { + if (vlineIntersectsAABB(TargetLoc, TargetLoc2, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax)) + { + return it->WeldableEdict; } } } @@ -2610,70 +2642,90 @@ edict_t* UTIL_GetDoorBlockingPathPoint(AvHAIPlayer* pBot, bot_path_node* PathNod { Vector TargetLoc = Vector(ToLoc.x, ToLoc.y, FromLoc.z); - UTIL_TraceLine(FromLoc, TargetLoc, ignore_monsters, dont_ignore_glass, (pBot != nullptr) ? pBot->Edict->v.pContainingEntity : nullptr, &doorHit); - if (!FNullEnt(SearchDoor)) { - if (doorHit.pHit == SearchDoor) { return doorHit.pHit; } + if (vlineIntersectsAABB(FromLoc, TargetLoc, SearchDoor->v.absmin, SearchDoor->v.absmax)) + { + return SearchDoor; + } } else { - if (!FNullEnt(doorHit.pHit)) + for (auto it = NavDoors.begin(); it != NavDoors.end(); it++) { - if (strcmp(STRING(doorHit.pHit->v.classname), "func_door") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_seethroughdoor") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_door_rotating") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "avhweldable") == 0) + if (vlineIntersectsAABB(FromLoc, TargetLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax)) { - return doorHit.pHit; + return it->DoorEdict; + } + } + + for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++) + { + if (vlineIntersectsAABB(FromLoc, TargetLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax)) + { + return it->WeldableEdict; } } } - UTIL_TraceLine(TargetLoc, ToLoc + Vector(0.0f, 0.0f, 10.0f), ignore_monsters, dont_ignore_glass, (pBot != nullptr) ? pBot->Edict->v.pContainingEntity : nullptr, &doorHit); + Vector NextTargetLoc = ToLoc + Vector(0.0f, 0.0f, 10.0f); if (!FNullEnt(SearchDoor)) { - if (doorHit.pHit == SearchDoor) { return doorHit.pHit; } + if (vlineIntersectsAABB(TargetLoc, NextTargetLoc, SearchDoor->v.absmin, SearchDoor->v.absmax)) + { + return SearchDoor; + } } else { - if (!FNullEnt(doorHit.pHit)) + for (auto it = NavDoors.begin(); it != NavDoors.end(); it++) { - if (strcmp(STRING(doorHit.pHit->v.classname), "func_door") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_seethroughdoor") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_door_rotating") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "avhweldable") == 0) + if (vlineIntersectsAABB(TargetLoc, NextTargetLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax)) { - return doorHit.pHit; + return it->DoorEdict; + } + } + + for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++) + { + if (vlineIntersectsAABB(TargetLoc, NextTargetLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax)) + { + return it->WeldableEdict; } } } } + Vector StartTrace = FromLoc + Vector(0.0f, 0.0f, 16.0f); Vector EndTrace = ToLoc + Vector(0.0f, 0.0f, 16.0f); - UTIL_TraceLine(StartTrace, EndTrace, ignore_monsters, dont_ignore_glass, (pBot != nullptr) ? pBot->Edict->v.pContainingEntity : nullptr, &doorHit); - if (!FNullEnt(SearchDoor)) { - if (doorHit.pHit == SearchDoor) { return doorHit.pHit; } + if (vlineIntersectsAABB(StartTrace, EndTrace, SearchDoor->v.absmin, SearchDoor->v.absmax)) + { + return SearchDoor; + } } else { - if (!FNullEnt(doorHit.pHit)) + for (auto it = NavDoors.begin(); it != NavDoors.end(); it++) { - if (strcmp(STRING(doorHit.pHit->v.classname), "func_door") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_seethroughdoor") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_door_rotating") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "avhweldable") == 0) + if (vlineIntersectsAABB(StartTrace, EndTrace, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax)) { - return doorHit.pHit; + return it->DoorEdict; + } + } + + for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++) + { + if (vlineIntersectsAABB(StartTrace, EndTrace, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax)) + { + return it->WeldableEdict; } } } - return nullptr; } @@ -2908,45 +2960,56 @@ edict_t* UTIL_GetDoorBlockingPathPoint(const Vector FromLocation, const Vector T { Vector TargetLoc = Vector(FromLoc.x, FromLoc.y, ToLocation.z); - UTIL_TraceLine(FromLoc, TargetLoc, ignore_monsters, dont_ignore_glass, nullptr, &doorHit); - if (!FNullEnt(SearchDoor)) { - if (doorHit.pHit == SearchDoor) { return doorHit.pHit; } + if (vlineIntersectsAABB(FromLoc, TargetLoc, SearchDoor->v.absmin, SearchDoor->v.absmax)) + { + return SearchDoor; + } } else { - if (!FNullEnt(doorHit.pHit)) + for (auto it = NavDoors.begin(); it != NavDoors.end(); it++) { - if (strcmp(STRING(doorHit.pHit->v.classname), "func_door") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_seethroughdoor") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_door_rotating") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "avhweldable") == 0 ) + if (vlineIntersectsAABB(FromLoc, TargetLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax)) { - return doorHit.pHit; + return it->DoorEdict; } } + for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++) + { + if (vlineIntersectsAABB(FromLoc, TargetLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax)) + { + return it->WeldableEdict; + } + } } Vector TargetLoc2 = Vector(ToLoc.x, ToLoc.y, ToLocation.z); - UTIL_TraceLine(TargetLoc, TargetLoc2, ignore_monsters, dont_ignore_glass, nullptr, &doorHit); - if (!FNullEnt(SearchDoor)) { - if (doorHit.pHit == SearchDoor) { return doorHit.pHit; } + if (vlineIntersectsAABB(FromLoc, TargetLoc2, SearchDoor->v.absmin, SearchDoor->v.absmax)) + { + return SearchDoor; + } } else { - if (!FNullEnt(doorHit.pHit)) + for (auto it = NavDoors.begin(); it != NavDoors.end(); it++) { - if (strcmp(STRING(doorHit.pHit->v.classname), "func_door") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_seethroughdoor") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_door_rotating") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "avhweldable") == 0) + if (vlineIntersectsAABB(FromLoc, TargetLoc2, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax)) { - return doorHit.pHit; + return it->DoorEdict; + } + } + + for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++) + { + if (vlineIntersectsAABB(FromLoc, TargetLoc2, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax)) + { + return it->WeldableEdict; } } } @@ -2956,64 +3019,84 @@ edict_t* UTIL_GetDoorBlockingPathPoint(const Vector FromLocation, const Vector T { Vector TargetLoc = Vector(ToLoc.x, ToLoc.y, FromLoc.z); - UTIL_TraceLine(FromLoc, TargetLoc, ignore_monsters, dont_ignore_glass, nullptr, &doorHit); - if (!FNullEnt(SearchDoor)) { - if (doorHit.pHit == SearchDoor) { return doorHit.pHit; } + if (vlineIntersectsAABB(FromLoc, TargetLoc, SearchDoor->v.absmin, SearchDoor->v.absmax)) + { + return SearchDoor; + } } else { - if (!FNullEnt(doorHit.pHit)) + for (auto it = NavDoors.begin(); it != NavDoors.end(); it++) { - if (strcmp(STRING(doorHit.pHit->v.classname), "func_door") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_seethroughdoor") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_door_rotating") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "avhweldable") == 0) + if (vlineIntersectsAABB(FromLoc, TargetLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax)) { - return doorHit.pHit; + return it->DoorEdict; + } + } + + for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++) + { + if (vlineIntersectsAABB(FromLoc, TargetLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax)) + { + return it->WeldableEdict; } } } - UTIL_TraceLine(TargetLoc, ToLoc + Vector(0.0f, 0.0f, 10.0f), ignore_monsters, dont_ignore_glass, nullptr, &doorHit); - - if (!FNullEnt(SearchDoor)) { - if (doorHit.pHit == SearchDoor) { return doorHit.pHit; } + if (vlineIntersectsAABB(TargetLoc, ToLoc, SearchDoor->v.absmin, SearchDoor->v.absmax)) + { + return SearchDoor; + } } else { - if (!FNullEnt(doorHit.pHit)) + for (auto it = NavDoors.begin(); it != NavDoors.end(); it++) { - if (strcmp(STRING(doorHit.pHit->v.classname), "func_door") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_seethroughdoor") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_door_rotating") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "avhweldable") == 0) + if (vlineIntersectsAABB(TargetLoc, ToLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax)) { - return doorHit.pHit; + return it->DoorEdict; + } + } + + for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++) + { + if (vlineIntersectsAABB(TargetLoc, ToLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax)) + { + return it->WeldableEdict; } } } + } - UTIL_TraceLine(FromLoc, ToLoc + Vector(0.0f, 0.0f, 10.0f), ignore_monsters, dont_ignore_glass, nullptr, &doorHit); + Vector TargetLoc = ToLoc + Vector(0.0f, 0.0f, 10.0f); if (!FNullEnt(SearchDoor)) { - if (doorHit.pHit == SearchDoor) { return doorHit.pHit; } + if (vlineIntersectsAABB(FromLoc, TargetLoc, SearchDoor->v.absmin, SearchDoor->v.absmax)) + { + return SearchDoor; + } } else { - if (!FNullEnt(doorHit.pHit)) + for (auto it = NavDoors.begin(); it != NavDoors.end(); it++) { - if (strcmp(STRING(doorHit.pHit->v.classname), "func_door") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_seethroughdoor") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "func_door_rotating") == 0 - || strcmp(STRING(doorHit.pHit->v.classname), "avhweldable") == 0) + if (vlineIntersectsAABB(FromLoc, TargetLoc, it->DoorEdict->v.absmin, it->DoorEdict->v.absmax)) { - return doorHit.pHit; + return it->DoorEdict; + } + } + + for (auto it = NavWeldableObstacles.begin(); it != NavWeldableObstacles.end(); it++) + { + if (vlineIntersectsAABB(FromLoc, TargetLoc, it->WeldableEdict->v.absmin, it->WeldableEdict->v.absmax)) + { + return it->WeldableEdict; } } } @@ -3406,7 +3489,9 @@ void GroundMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoin LeapDist = UTIL_MetresToGoldSrcUnits(1.0f); } - if (CanBotLeap(pBot) && vDist2DSq(pBot->Edict->v.origin, EndPoint) > sqrf(LeapDist) && UTIL_PointIsDirectlyReachable(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, EndPoint)) + bool bIsAmbush = (pBot->BotNavInfo.MoveStyle == MOVESTYLE_AMBUSH); + + if (!bIsAmbush && CanBotLeap(pBot) && vDist2DSq(pBot->Edict->v.origin, EndPoint) > sqrf(LeapDist) && UTIL_PointIsDirectlyReachable(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, EndPoint)) { float CombatWeaponEnergyCost = GetEnergyCostForWeapon(pBot->DesiredCombatWeapon); float RequiredEnergy = (CombatWeaponEnergyCost + GetLeapCost(pBot)) - (GetPlayerEnergyRegenPerSecond(pEdict) * 0.5f); // We allow for around .5s of regen time as well @@ -7770,7 +7855,6 @@ void UTIL_PopulateTriggersForEntity(edict_t* Entity, vector& Trigge void UTIL_PopulateWeldableObstacles() { - UTIL_ClearWeldablesData(); CBaseEntity* currWeldable = NULL; while (((currWeldable = UTIL_FindEntityByClassname(currWeldable, "avhweldable")) != NULL)) @@ -8394,6 +8478,7 @@ void UTIL_PopulateDoors() NewDoor.DoorEntity = ToggleRef; NewDoor.DoorEdict = DoorEnt->edict(); NewDoor.CurrentState = ToggleRef->m_toggle_state; + NewDoor.DoorName = STRING(NewDoor.DoorEdict->v.targetname); const char* DoorName = STRING(NewDoor.DoorEdict->v.targetname); diff --git a/main/source/mod/AvHAINavigation.h b/main/source/mod/AvHAINavigation.h index ed682b64..56be92d2 100644 --- a/main/source/mod/AvHAINavigation.h +++ b/main/source/mod/AvHAINavigation.h @@ -88,6 +88,7 @@ typedef struct _NAV_DOOR vector StopPoints; // Where does this door/platform stop when triggered? NavDoorType DoorType = DOORTYPE_DOOR; vector AffectedConnections; + const char* DoorName; } nav_door; typedef struct _NAV_WELDABLE diff --git a/main/source/mod/AvHAIPlayer.cpp b/main/source/mod/AvHAIPlayer.cpp index 540ba70e..6f876c6c 100644 --- a/main/source/mod/AvHAIPlayer.cpp +++ b/main/source/mod/AvHAIPlayer.cpp @@ -465,11 +465,18 @@ void BotAlienAttackNonPlayerTarget(AvHAIPlayer* pBot, edict_t* Target) } } - if (IsPlayerLerk(pBot->Edict)) + bool bPlayerCloaked = pBot->Player->GetOpacity() < 0.5f && !GetHasUpgrade(pBot->Edict->v.iuser4, MASK_SENSORY_NEARBY); + + if (IsPlayerLerk(pBot->Edict) || bPlayerCloaked) { if (AITAC_ShouldBotBeCautious(pBot)) { - MoveTo(pBot, AttackPoint, MOVESTYLE_HIDE, 100.0f); + if (bPlayerCloaked) + { + pBot->BotNavInfo.bShouldWalk = true; + } + + MoveTo(pBot, AttackPoint, MOVESTYLE_AMBUSH, 100.0f); } else { @@ -1398,7 +1405,9 @@ void BotUpdateView(AvHAIPlayer* pBot) float bot_reaction_time = (IsPlayerMarine(pBot->Edict)) ? pBot->BotSkillSettings.marine_bot_reaction_time : pBot->BotSkillSettings.alien_bot_reaction_time; - bool bIsVisible = (bInFOV && (bHasLOS || bIsTracked)); + bool bIsPlayerInvisible = UTIL_IsCloakedPlayerInvisible(pBot->Edict, PlayerRef); + + bool bIsVisible = !bIsPlayerInvisible && (bInFOV && (bHasLOS || bIsTracked)); if (bIsVisible != TrackingInfo->bIsVisible) { @@ -1416,7 +1425,9 @@ void BotUpdateView(AvHAIPlayer* pBot) TrackingInfo->bHasLOS = bHasLOS; - if (bInFOV && (bHasLOS || bIsTracked)) + bool bCanSeeEnemy = (!bIsPlayerInvisible && bHasLOS); + + if (bInFOV && (bCanSeeEnemy || bIsTracked)) { Vector FloorLocation = UTIL_GetEntityGroundLocation(Enemy); Vector BotVelocity = Enemy->v.velocity; @@ -1462,7 +1473,7 @@ void BotUpdateView(AvHAIPlayer* pBot) continue; } - if (!bInFOV || !bHasLOS) + if (!bInFOV || !bCanSeeEnemy) { if (gpGlobals->time < TrackingInfo->EndTrackingTime) { @@ -1540,6 +1551,35 @@ void BotUpdateView(AvHAIPlayer* pBot) } } +bool UTIL_IsCloakedPlayerInvisible(edict_t* Observer, AvHPlayer* Player) +{ + if (Player->GetOpacity() > 0.6f) { return false; } + + if (Player->GetIsCloaked()) { return true; } + + switch (Player->GetUser3()) + { + case AVH_USER3_ALIEN_PLAYER1: + case AVH_USER3_ALIEN_PLAYER2: + case AVH_USER3_ALIEN_PLAYER3: + { + if (Player->GetOpacity() < 0.3f) { return true; } + + return (vDist3DSq(Observer->v.origin, Player->pev->origin) > sqrf(UTIL_MetresToGoldSrcUnits(10.0f)) || Player->pev->velocity.Length2D() < 50.0f); + } + case AVH_USER3_ALIEN_PLAYER4: + case AVH_USER3_ALIEN_PLAYER5: + { + if (Player->GetOpacity() > 0.4f) { return false; } + if (Player->GetOpacity() < 0.2f) { return true; } + + return vDist3DSq(Observer->v.origin, Player->pev->origin) > sqrf(UTIL_MetresToGoldSrcUnits(10.0f)); + } + } + + return false; +} + void BotClearEnemyTrackingInfo(enemy_status* TrackingInfo) { TrackingInfo->bIsVisible = false; @@ -1848,64 +1888,11 @@ void EndBotFrame(AvHAIPlayer* pBot) void CustomThink(AvHAIPlayer* pBot) { - if (IsPlayerMarine(pBot->Player)) + if (IsPlayerMarine(pBot->Edict)) { return; } + + if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_SENSORY)) { - if (!PlayerHasWeapon(pBot->Player, WEAPON_MARINE_MINES)) - { - AvHAIDroppedItem* NearestMines = AITAC_FindClosestItemToLocation(pBot->Edict->v.origin, DEPLOYABLE_ITEM_MINES, pBot->Player->GetTeam(), AI_REACHABILITY_MARINE, 0.0f, 5000.0f, false); - - if (NearestMines) - { - AITASK_SetPickupTask(pBot, &pBot->PrimaryBotTask, NearestMines->edict, true); - } - } - else - { - DeployableSearchFilter MineStructureFilter; - MineStructureFilter.DeployableTeam = pBot->Player->GetTeam(); - MineStructureFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; - MineStructureFilter.ReachabilityTeam = pBot->Player->GetTeam(); - MineStructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; - - AvHAIBuildableStructure NearestIP = AITAC_FindClosestDeployableToLocation(pBot->Edict->v.origin, &MineStructureFilter); - - if (NearestIP.IsValid()) - { - AITASK_SetMineStructureTask(pBot, &pBot->PrimaryBotTask, NearestIP.edict, true); - } - } - - BotProgressTask(pBot, &pBot->PrimaryBotTask); - - return; - } - - - if (IsPlayerMarine(pBot->Player)) - { - pBot->CurrentEnemy = BotGetNextEnemyTarget(pBot); - - if (pBot->CurrentEnemy < 0) - { - MoveTo(pBot, AITAC_GetTeamStartingLocation(AIMGR_GetEnemyTeam(pBot->Player->GetTeam())), MOVESTYLE_NORMAL); - } - else - { - MarineCombatThink(pBot); - } - - return; - } - - if (!IsPlayerOnos(pBot->Edict)) - { - if (pBot->Player->GetResources() < BALANCE_VAR(kOnosCost)) - { - pBot->Player->GiveResources(70.0f); - } - - BotEvolveLifeform(pBot, pBot->CurrentFloorPosition, ALIEN_LIFEFORM_FIVE); - + BotEvolveUpgrade(pBot, pBot->CurrentFloorPosition, ALIEN_EVOLUTION_TEN); return; } @@ -1918,8 +1905,7 @@ void CustomThink(AvHAIPlayer* pBot) else { AlienCombatThink(pBot); - } - + } } void DroneThink(AvHAIPlayer* pBot) @@ -2344,6 +2330,9 @@ AvHAICombatStrategy GetSkulkCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_sta return COMBAT_STRATEGY_ATTACK; } + // We're invisible, so go get them + if (pBot->Player->GetOpacity() < 0.1f) { return COMBAT_STRATEGY_ATTACK; } + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); @@ -2955,7 +2944,7 @@ bool RegularMarineCombatThink(AvHAIPlayer* pBot) AvHAIBuildableStructure NearestArmouryRef = AITAC_FindClosestDeployableToLocation(pBot->Edict->v.origin, &NearestArmoury); - if (NearestArmouryRef.IsValid() && !IsAreaAffectedBySpores(NearestArmouryRef.Location)) + if (NearestArmouryRef.IsValid() && (!IsAreaAffectedBySpores(NearestArmouryRef.Location) || PlayerHasHeavyArmour(pBot->Edict))) { if (!TrackedEnemyRef->bHasLOS || (IsPlayerAlien(pBot->Edict) && vDist2DSq(NearestArmouryRef.Location, CurrentEnemy->v.origin) > sqrf(UTIL_MetresToGoldSrcUnits(10.0f)))) { @@ -3299,7 +3288,7 @@ void AIPlayerSetMarineSweeperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Tas AvHAIBuildableStructure UnbuiltIP = AITAC_FindClosestDeployableToLocation(CommChairLocation, &StructureFilter); - if (UnbuiltIP.IsValid()) + if (UnbuiltIP.IsValid() && (!IsAreaAffectedBySpores(UnbuiltIP.Location) || PlayerHasHeavyArmour(pBot->Edict))) { AITASK_SetBuildTask(pBot, Task, UnbuiltIP.edict, true); return; @@ -3310,7 +3299,7 @@ void AIPlayerSetMarineSweeperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Tas AvHAIBuildableStructure UnbuiltStructure = AITAC_FindClosestDeployableToLocation(CommChairLocation, &StructureFilter); - if (UnbuiltStructure.IsValid()) + if (UnbuiltStructure.IsValid() && (!IsAreaAffectedBySpores(UnbuiltStructure.Location) || PlayerHasHeavyArmour(pBot->Edict))) { AITASK_SetBuildTask(pBot, Task, UnbuiltStructure.edict, true); return; @@ -3340,7 +3329,7 @@ void AIPlayerSetMarineSweeperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Tas AvHAIBuildableStructure AttackedStructure = AITAC_FindClosestDeployableToLocation(CommChairLocation, &AttackedStructureFilter); - if (AttackedStructure.IsValid()) + if (AttackedStructure.IsValid() && (!IsAreaAffectedBySpores(AttackedStructure.Location) || PlayerHasHeavyArmour(pBot->Edict))) { AITASK_SetWeldTask(pBot, Task, AttackedStructure.edict, true); return; @@ -3632,11 +3621,14 @@ void AIPlayerSetWantsAndNeedsCOMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Ta // We really need some health or ammo, hit the armoury if (NearestArmoury.IsValid()) { - Task->TaskType = TASK_RESUPPLY; - Task->bTaskIsUrgent = true; - Task->TaskLocation = NearestArmoury.Location; - Task->TaskTarget = NearestArmoury.edict; - return; + if (!IsAreaAffectedBySpores(NearestArmoury.Location) || PlayerHasHeavyArmour(pBot->Edict)) + { + Task->TaskType = TASK_RESUPPLY; + Task->bTaskIsUrgent = true; + Task->TaskLocation = NearestArmoury.Location; + Task->TaskTarget = NearestArmoury.edict; + return; + } } } @@ -3691,11 +3683,14 @@ void AIPlayerSetWantsAndNeedsMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task // We really need some health or ammo, either hit the armoury, or ask for a resupply if (NearestArmoury.IsValid()) { - Task->TaskType = TASK_RESUPPLY; - Task->bTaskIsUrgent = true; - Task->TaskLocation = NearestArmoury.Location; - Task->TaskTarget = NearestArmoury.edict; - return; + if (!IsAreaAffectedBySpores(NearestArmoury.Location) || PlayerHasHeavyArmour(pBot->Edict)) + { + Task->TaskType = TASK_RESUPPLY; + Task->bTaskIsUrgent = true; + Task->TaskLocation = NearestArmoury.Location; + Task->TaskTarget = NearestArmoury.edict; + return; + } } else { @@ -3971,7 +3966,7 @@ void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) AvHAIBuildableStructure UnbuiltIP = AITAC_FindClosestDeployableToLocation(pBot->Edict->v.origin, &UnbuiltFilter); - if (UnbuiltIP.IsValid()) + if (UnbuiltIP.IsValid() && (!IsAreaAffectedBySpores(UnbuiltIP.Location) || PlayerHasHeavyArmour(pBot->Edict))) { float ThisDist = vDist2D(UnbuiltIP.Location, pBot->Edict->v.origin); int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(BotTeam, UnbuiltIP.Location, ThisDist - 5.0f, false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); @@ -4009,7 +4004,7 @@ void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) } } - if (NearestStructure.IsValid()) + if (NearestStructure.IsValid() && (!IsAreaAffectedBySpores(NearestStructure.Location) || PlayerHasHeavyArmour(pBot->Edict))) { AITASK_SetBuildTask(pBot, Task, NearestStructure.edict, true); return; @@ -4121,6 +4116,8 @@ void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { AvHAIBuildableStructure ThisStructure = (*it); + if (ThisStructure.edict->v.waterlevel > 0) { continue; } + int NumMines = AITAC_GetNumDeployablesNearLocation(ThisStructure.Location, &MineFilter); if (NumMines < 4) @@ -4168,8 +4165,6 @@ void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) } } - - } bool AIPlayerMustFinishCurrentTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) @@ -5351,6 +5346,10 @@ void AIPlayerThink(AvHAIPlayer* pBot) { TestNavThink(pBot); } + else if (avh_botdebugmode.value == 3) + { + CustomThink(pBot); + } else { switch (GetGameRules()->GetMapMode()) @@ -5479,7 +5478,7 @@ void BotSwitchToWeapon(AvHAIPlayer* pBot, AvHAIWeapon NewWeaponSlot) bool ShouldBotThink(AvHAIPlayer* pBot) { - return NavmeshLoaded() && GetGameRules()->GetGameStarted() && (IsPlayerActiveInGame(pBot->Edict) || IsPlayerCommander(pBot->Edict)) && !IsPlayerGestating(pBot->Edict); + return NavmeshLoaded() && GetGameRules()->GetGameStarted() && !AIMGR_HasMatchEnded() && (IsPlayerActiveInGame(pBot->Edict) || IsPlayerCommander(pBot->Edict)) && !IsPlayerGestating(pBot->Edict); } void BotResumePlay(AvHAIPlayer* pBot) @@ -6904,6 +6903,15 @@ void AIPlayerSetSecondaryAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) return; } + // If we're engaging an enemy turret then finish the job + if (Task->TaskType == TASK_ATTACK) + { + if (vDist2DSq(pBot->Edict->v.origin, Task->TaskTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(20.0f))) + { + return; + } + } + DeployableSearchFilter AttackedStructuresFilter; AttackedStructuresFilter.DeployableTypes = (IsPlayerLerk(pBot->Edict)) ? SEARCH_ALL_STRUCTURES : STRUCTURE_ALIEN_RESTOWER; AttackedStructuresFilter.DeployableTeam = BotTeam; @@ -6945,6 +6953,24 @@ void AIPlayerSetSecondaryAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) return; } + DeployableSearchFilter EnemyStructures; + EnemyStructures.DeployableTypes = SEARCH_ALL_STRUCTURES; + EnemyStructures.DeployableTeam = BotTeam; + EnemyStructures.ReachabilityTeam = BotTeam; + EnemyStructures.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + EnemyStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); + + vector NearbyEnemyStructures = AITAC_FindAllDeployables(pBot->Edict->v.origin, &EnemyStructures); + + for (auto it = NearbyEnemyStructures.begin(); it != NearbyEnemyStructures.end(); it++) + { + if (UTIL_PlayerHasLOSToEntity(pBot->Edict, it->edict, UTIL_MetresToGoldSrcUnits(20.0f), false)) + { + AITASK_SetAttackTask(pBot, Task, it->edict, false); + return; + } + } + } void AIPlayerSetWantsAndNeedsAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) @@ -7200,6 +7226,8 @@ bool SkulkCombatThink(AvHAIPlayer* pBot) if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_ATTACK || (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_AMBUSH && bShouldBreakAmbush)) { + bool bIsCloaked = (UTIL_IsCloakedPlayerInvisible(CurrentEnemy, pBot->Player) || pBot->Player->GetOpacity() < 0.5f); + AvHAIWeapon DesiredWeapon = WEAPON_SKULK_BITE; // If we have xenocide, then choose it if we have lots of good targets in blast radius @@ -7235,7 +7263,7 @@ bool SkulkCombatThink(AvHAIPlayer* pBot) } } - if (DesiredWeapon != WEAPON_SKULK_XENOCIDE) + if (!bIsCloaked && DesiredWeapon != WEAPON_SKULK_XENOCIDE) { if (!IsPlayerParasited(CurrentEnemy) && DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { @@ -7267,9 +7295,12 @@ bool SkulkCombatThink(AvHAIPlayer* pBot) } } - MoveTo(pBot, MoveTarget, MOVESTYLE_NORMAL); + BotMoveStyle DesiredMoveStyle = (bIsCloaked) ? MOVESTYLE_AMBUSH : MOVESTYLE_NORMAL; + pBot->BotNavInfo.bShouldWalk = bIsCloaked && !GetHasUpgrade(pBot->Edict->v.iuser4, MASK_SENSORY_NEARBY); - if (DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) + MoveTo(pBot, MoveTarget, DesiredMoveStyle); + + if (!bIsCloaked && DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { if (CanBotLeap(pBot)) { diff --git a/main/source/mod/AvHAIPlayer.h b/main/source/mod/AvHAIPlayer.h index 4d64ce20..b407e769 100644 --- a/main/source/mod/AvHAIPlayer.h +++ b/main/source/mod/AvHAIPlayer.h @@ -66,6 +66,8 @@ void BotClearEnemyTrackingInfo(enemy_status* TrackingInfo); bool IsPlayerInBotFOV(AvHAIPlayer* Observer, edict_t* TargetPlayer); void UpdateAIPlayerViewFrustum(AvHAIPlayer* pBot); +bool UTIL_IsCloakedPlayerInvisible(edict_t* Observer, AvHPlayer* Player); + Vector GetVisiblePointOnPlayerFromObserver(edict_t* Observer, edict_t* TargetPlayer); diff --git a/main/source/mod/AvHAIPlayerManager.cpp b/main/source/mod/AvHAIPlayerManager.cpp index 14b877c9..c1653b97 100644 --- a/main/source/mod/AvHAIPlayerManager.cpp +++ b/main/source/mod/AvHAIPlayerManager.cpp @@ -101,7 +101,7 @@ void AIMGR_UpdateAIPlayerCounts() // If bots are disabled or we've exceeded max AI time and no humans are playing, ensure we've removed all bots from the game // Max AI time is configurable in nsbots.ini, and helps prevent infinite stalemates // Default time is 90 minutes before bots start leaving to let the map cycle - if (!AIMGR_IsBotEnabled() || (bMatchExceededMaxLength && AIMGR_GetNumActiveHumanPlayers() == 0)) + if (!AIMGR_IsBotEnabled() || (bMatchExceededMaxLength && AIMGR_GetNumActiveHumanPlayers() == 0) || (AIMGR_HasMatchEnded() && gpGlobals->time - GetGameRules()->GetVictoryTime() > 5.0f)) { if (AIMGR_GetNumAIPlayers() > 0) { @@ -113,38 +113,6 @@ void AIMGR_UpdateAIPlayerCounts() if (!AIMGR_ShouldStartPlayerBalancing()) { return; } - // If game has ended, kick bots that have dropped back to the ready room - if (GetGameRules()->GetVictoryTeam() != TEAM_IND) - { - AIMGR_RemoveBotsInReadyRoom(); - return; - } - - - - BotFillTiming CurrentFillTiming = CONFIG_GetBotFillTiming(); - - if (!GetGameRules()->GetGameStarted()) - { - if (CurrentFillTiming == FILLTIMING_ROUNDSTART) { return; } // Do nothing if we're only meant to add bots after round start, and the round hasn't started - - if (CurrentFillTiming == FILLTIMING_ALLHUMANS) - { - for (int i = 1; i <= gpGlobals->maxClients; i++) - { - edict_t* PlayerEdict = INDEXENT(i); - if (FNullEnt(PlayerEdict) || PlayerEdict->free || (PlayerEdict->v.flags & FL_FAKECLIENT)) { continue; } // Ignore fake clients - - AvHPlayer* PlayerRef = dynamic_cast(CBaseEntity::Instance(PlayerEdict)); - - if (!PlayerRef) { continue; } - - if (PlayerRef->GetInReadyRoom()) { return; } // If there is a human in the ready room, don't add any more bots - } - } - } - - if (avh_botautomode.value == 1) // Fill teams: bots will be added and removed to maintain a minimum player count { AIMGR_UpdateFillTeams(); @@ -298,6 +266,28 @@ void AIMGR_RemoveAIPlayerFromTeam(int Team) AvHTeamNumber teamA = GetGameRules()->GetTeamANumber(); AvHTeamNumber teamB = GetGameRules()->GetTeamBNumber(); + if (AIMGR_HasMatchEnded() && Team == 0) + { + vector::iterator ItemToRemove = ActiveAIPlayers.end(); // Current bot to be kicked + + for (auto it = ActiveAIPlayers.begin(); it != ActiveAIPlayers.end(); it++) + { + if (IsPlayerInReadyRoom(it->Edict)) + { + ItemToRemove = it; + break; + } + } + + if (ItemToRemove != ActiveAIPlayers.end()) + { + ItemToRemove->Player->Kick(); + + ActiveAIPlayers.erase(ItemToRemove); + return; + } + } + if (Team > 0) { DesiredTeam = (Team == 1) ? teamA : teamB; @@ -1190,7 +1180,43 @@ vector AIMGR_GetNonAIPlayersOnTeam(AvHTeamNumber Team) bool AIMGR_ShouldStartPlayerBalancing() { - return (bPlayerSpawned && gpGlobals->time - AIStartedTime > AI_GRACE_PERIOD) || (gpGlobals->time - AIStartedTime > AI_MAX_START_TIMEOUT); + if (gpGlobals->time - AIStartedTime < AI_GRACE_PERIOD) { return false; } + + if (AIMGR_HasMatchEnded()) { return false; } + + BotFillTiming FillTiming = CONFIG_GetBotFillTiming(); + + switch (FillTiming) + { + case FILLTIMING_MAPLOAD: + return true; + case FILLTIMING_ROUNDSTART: + return GetGameRules()->GetGameStarted(); + default: + break; + } + + if (!bPlayerSpawned) + { + return (gpGlobals->time - AIStartedTime > AI_MAX_START_TIMEOUT); + } + + // We've started adding bots, keep going + if (AIMGR_GetNumAIPlayers() > 0) { return true; } + + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + edict_t* PlayerEdict = INDEXENT(i); + if (FNullEnt(PlayerEdict) || PlayerEdict->free || (PlayerEdict->v.flags & FL_FAKECLIENT)) { continue; } // Ignore fake clients + + AvHPlayer* PlayerRef = dynamic_cast(CBaseEntity::Instance(PlayerEdict)); + + if (!PlayerRef) { continue; } + + if (PlayerRef->GetInReadyRoom()) { return false; } // If there is a human in the ready room, don't add any more bots + } + + return true; } void AIMGR_UpdateAIMapData() @@ -1280,6 +1306,9 @@ void AIMGR_OnBotEnabled() UnloadNavigationData(); } + CONFIG_ParseConfigFile(); + CONFIG_PopulateBotNames(); + AITAC_ClearMapAIData(true); bBotsEnabled = true; @@ -1353,13 +1382,31 @@ void AIMGR_UpdateAISystem() if (AIMGR_IsBotEnabled()) { - if (AIMGR_GetNavMeshStatus() == NAVMESH_STATUS_PENDING) + if (!AIMGR_HasMatchEnded()) { - AIMGR_LoadNavigationData(); - } + if (AIMGR_GetNavMeshStatus() == NAVMESH_STATUS_PENDING) + { + AIMGR_LoadNavigationData(); + } - AIMGR_UpdateAIMapData(); + AIMGR_UpdateAIMapData(); + } AIMGR_UpdateAIPlayers(); } +} + +bool AIMGR_HasMatchEnded() +{ + // Game has finished + if (GetGameRules()->GetVictoryTeam() != TEAM_IND) { return true; } + + // Game is still going, but if it's exceeded the max AI match time and there are no humans playing, consider the match over + // Helps prevent stalemates if bots get stuck and keeps map rotations going + float MaxMinutes = CONFIG_GetMaxAIMatchTimeMinutes(); + float MaxSeconds = MaxMinutes * 60.0f; + + bool bMatchExceededMaxLength = (GetGameRules()->GetGameTime() > MaxSeconds); + + return (bMatchExceededMaxLength && AIMGR_GetNumActiveHumanPlayers() == 0); } \ No newline at end of file diff --git a/main/source/mod/AvHAIPlayerManager.h b/main/source/mod/AvHAIPlayerManager.h index 530ad9a9..1057bafe 100644 --- a/main/source/mod/AvHAIPlayerManager.h +++ b/main/source/mod/AvHAIPlayerManager.h @@ -122,4 +122,6 @@ void AIMGR_OnBotDisabled(); void AIMGR_UpdateAISystem(); +bool AIMGR_HasMatchEnded(); + #endif \ No newline at end of file diff --git a/main/source/mod/AvHAITask.cpp b/main/source/mod/AvHAITask.cpp index 0e0661b1..3fbe5c70 100644 --- a/main/source/mod/AvHAITask.cpp +++ b/main/source/mod/AvHAITask.cpp @@ -563,6 +563,8 @@ bool AITASK_IsResupplyTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (!UTIL_StructureIsFullyBuilt(Task->TaskTarget) || UTIL_StructureIsRecycling(Task->TaskTarget)) { return false; } + if (IsAreaAffectedBySpores(Task->TaskTarget->v.origin) && !PlayerHasHeavyArmour(pBot->Edict)) { return false; } + return ( (pBot->Edict->v.health < pBot->Edict->v.max_health) || (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryMaxAmmoReserve(pBot->Player)) @@ -1095,11 +1097,18 @@ void BotProgressMoveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { Task->TaskStartedTime = gpGlobals->time; - if (IsPlayerLerk(pBot->Edict)) + bool bPlayerCloaked = pBot->Player->GetOpacity() < 0.5f && !GetHasUpgrade(pBot->Edict->v.iuser4, MASK_SENSORY_NEARBY); + + if (IsPlayerLerk(pBot->Edict) || bPlayerCloaked) { if (AITAC_ShouldBotBeCautious(pBot)) { - MoveTo(pBot, Task->TaskLocation, MOVESTYLE_HIDE, 100.0f); + if (bPlayerCloaked) + { + pBot->BotNavInfo.bShouldWalk = true; + } + + MoveTo(pBot, Task->TaskLocation, MOVESTYLE_AMBUSH, 100.0f); } else { @@ -1649,11 +1658,19 @@ void BotProgressGuardTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation) > sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { Task->TaskStartedTime = 0.0f; - if (IsPlayerLerk(pBot->Edict)) + + bool bPlayerCloaked = pBot->Player->GetOpacity() < 0.5f && !GetHasUpgrade(pBot->Edict->v.iuser4, MASK_SENSORY_NEARBY); + + if (IsPlayerLerk(pBot->Edict) || bPlayerCloaked) { if (AITAC_ShouldBotBeCautious(pBot)) { - MoveTo(pBot, Task->TaskLocation, MOVESTYLE_HIDE, 100.0f); + if (bPlayerCloaked) + { + pBot->BotNavInfo.bShouldWalk = true; + } + + MoveTo(pBot, Task->TaskLocation, MOVESTYLE_AMBUSH, 100.0f); } else {