diff --git a/main/source/mod/AIPlayers/AvHAIConstants.h b/main/source/mod/AIPlayers/AvHAIConstants.h index 49881d0e..45528415 100644 --- a/main/source/mod/AIPlayers/AvHAIConstants.h +++ b/main/source/mod/AIPlayers/AvHAIConstants.h @@ -13,6 +13,7 @@ static const float commander_action_cooldown = 1.0f; static const float min_request_spam_time = 5.0f; constexpr auto MAX_AI_PATH_SIZE = 512; // Maximum number of points allowed in a path (this should be enough for any sized map) +static const int MAX_NAV_MESHES = 8; // Max number of nav meshes allowed. Currently 3 are used (one for building placement, one for the onos, and a regular one for everyone else) // NS weapon types. Each number refers to the GoldSrc weapon index typedef enum @@ -168,6 +169,15 @@ typedef enum _STRUCTUREPURPOSE } StructurePurpose; +typedef struct _OFF_MESH_CONN +{ + int MeshConnectionIndex = -1; + unsigned short ConnectionFlags = 0; + Vector FromLocation = g_vecZero; + Vector ToLocation = g_vecZero; + edict_t* TargetObject = nullptr; +} AvHAIOffMeshConnection; + // Data structure used to track resource nodes in the map typedef struct _RESOURCE_NODE { @@ -192,7 +202,7 @@ typedef struct _HIVE_DEFINITION_T AvHMessageID TechStatus = MESSAGE_NULL; // What tech (if any) is assigned to this hive right now bool bIsUnderAttack = false; // Is the hive currently under attack? Becomes false if not taken damage for more than 10 seconds AvHAIResourceNode* HiveResNodeRef = nullptr; // Which resource node (indexes into ResourceNodes array) belongs to this hive? - unsigned int ObstacleRefs[8]; // When in progress or built, will place an obstacle so bots don't try to walk through it + unsigned int ObstacleRefs[MAX_NAV_MESHES]; // When in progress or built, will place an obstacle so bots don't try to walk through it float NextFloorLocationCheck = 0.0f; // When should the closest navigable point to the hive be calculated? Used to delay the check after a hive is built AvHTeamNumber OwningTeam = TEAM_IND; // Which team owns this hive currently (TEAM_IND if empty) @@ -254,7 +264,8 @@ typedef struct _AVH_AI_BUILDABLE_STRUCTURE unsigned int TeamAReachabilityFlags = AI_REACHABILITY_NONE; unsigned int TeamBReachabilityFlags = AI_REACHABILITY_NONE; int LastSeen = 0; // Which refresh cycle was this last seen on? Used to determine if the building has been removed from play - unsigned int ObstacleRefs[8]; // References to this structure's obstacles across each nav mesh + unsigned int ObstacleRefs[MAX_NAV_MESHES]; // References to this structure's obstacles across each nav mesh + vector OffMeshConnections; // References to any off-mesh connections this structure is associated with Vector LastSuccessfulCommanderLocation = g_vecZero; // Tracks the last commander view location where it successfully placed or selected the building Vector LastSuccessfulCommanderAngle = g_vecZero; // Tracks the last commander input angle ("click" location) used to successfully place or select building StructurePurpose Purpose = STRUCTURE_PURPOSE_NONE; diff --git a/main/source/mod/AIPlayers/AvHAIHelper.cpp b/main/source/mod/AIPlayers/AvHAIHelper.cpp index 64d9e08f..39d1facc 100644 --- a/main/source/mod/AIPlayers/AvHAIHelper.cpp +++ b/main/source/mod/AIPlayers/AvHAIHelper.cpp @@ -272,7 +272,8 @@ void AIDEBUG_DrawBotPath(AvHAIPlayer* pBot) case SAMPLE_POLYFLAGS_BLOCKED: UTIL_DrawLine(INDEXENT(1), FromLoc, ToLoc, 128, 128, 128); break; - case SAMPLE_POLYFLAGS_PHASEGATE: + case SAMPLE_POLYFLAGS_TEAM1PHASEGATE: + case SAMPLE_POLYFLAGS_TEAM2PHASEGATE: UTIL_DrawLine(INDEXENT(1), FromLoc, ToLoc, 255, 128, 128); break; default: diff --git a/main/source/mod/AIPlayers/AvHAINavigation.cpp b/main/source/mod/AIPlayers/AvHAINavigation.cpp index 3287bf5d..9bf9ca96 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.cpp +++ b/main/source/mod/AIPlayers/AvHAINavigation.cpp @@ -17,6 +17,7 @@ #include "../AvHWeldable.h" #include "../AvHServerUtil.h" +#include "../AvHGamerules.h" #include "../../dlls/triggers.h" @@ -104,13 +105,13 @@ struct NavMeshTileHeader struct OffMeshConnectionDef { - bool bIsActive = false; + unsigned int UserID = 0; float spos[3] = { 0.0f, 0.0f, 0.0f }; float epos[3] = { 0.0f, 0.0f, 0.0f }; bool bBiDir = false; float Rad = 0.0f; - char Area = 0; - short Flag = 0; + unsigned char Area = 0; + unsigned short Flag = 0; }; struct FastLZCompressor : public dtTileCacheCompressor @@ -192,8 +193,14 @@ struct MeshProcess : public dtTileCacheMeshProcess unsigned short OffMeshFlags[MAX_OFFMESH_CONNS]; unsigned int OffMeshIDs[MAX_OFFMESH_CONNS]; + bool bNavDataDirty = false; + OffMeshConnectionDef ConnectionDefinitions[MAX_OFFMESH_CONNS]; + vector OffMeshConnections; + + unsigned int NextUserID = 0; + inline MeshProcess() {} @@ -202,57 +209,113 @@ struct MeshProcess : public dtTileCacheMeshProcess } - int AddOffMeshConnectionDef(Vector Start, Vector End, unsigned char area, unsigned short flag, bool bBiDirectional) + void AddOffMeshConnectionDef(Vector Start, Vector End, unsigned char area, unsigned short flag, bool bBiDirectional, AvHAIOffMeshConnection* ConnectionRef) { - float spos[3] = { Start.x, Start.z, -Start.y }; - float epos[3] = { End.x, End.z, -End.y }; + OffMeshConnectionDef NewDefinition; + NewDefinition.Area = area; + NewDefinition.bBiDir = bBiDirectional; + NewDefinition.spos[0] = Start.x; + NewDefinition.spos[1] = Start.z; + NewDefinition.spos[2] = -Start.y; + NewDefinition.epos[0] = End.x; + NewDefinition.epos[1] = End.z; + NewDefinition.epos[2] = -End.y; + NewDefinition.Flag = flag; + NewDefinition.Rad = 18.0f; + NewDefinition.UserID = NextUserID; - if (NumOffMeshConns >= MAX_OFFMESH_CONNS) return -1; - float* v = &OffMeshVerts[NumOffMeshConns * 3 * 2]; - OffMeshRads[NumOffMeshConns] = 18.0f; - OffMeshDirs[NumOffMeshConns] = bBiDirectional; - OffMeshAreas[NumOffMeshConns] = area; - OffMeshFlags[NumOffMeshConns] = flag; - OffMeshIDs[NumOffMeshConns] = 1000 + NumOffMeshConns; - dtVcopy(&v[0], spos); - dtVcopy(&v[3], epos); - NumOffMeshConns++; - return NumOffMeshConns - 1; - } - - void RemoveOffMeshConnectionDef(int Index) - { - if (Index > -1 && Index < MAX_OFFMESH_CONNS) + if (ConnectionRef) { - NumOffMeshConns--; - float* src = &OffMeshVerts[NumOffMeshConns * 3 * 2]; - float* dst = &OffMeshVerts[Index * 3 * 2]; - dtVcopy(&dst[0], &src[0]); - dtVcopy(&dst[3], &src[3]); - OffMeshRads[Index] = OffMeshRads[NumOffMeshConns]; - OffMeshDirs[Index] = OffMeshDirs[NumOffMeshConns]; - OffMeshAreas[Index] = OffMeshAreas[NumOffMeshConns]; - OffMeshFlags[Index] = OffMeshFlags[NumOffMeshConns]; + ConnectionRef->MeshConnectionIndex = NextUserID; } + + NextUserID++; + + OffMeshConnections.push_back(NewDefinition); + + bNavDataDirty = true; + }; + + void RemoveOffMeshConnectionDef(int UserID) + { + for (auto it = OffMeshConnections.begin(); it != OffMeshConnections.end();) + { + if (it->UserID == UserID) + { + it = OffMeshConnections.erase(it); + } + else + { + it++; + } + } + + bNavDataDirty = true; } - void GetOffMeshConnectionPoints(int Index, Vector& OutStartLoc, Vector& OutEndLoc) + void UpdateOffMeshData() + { + int CurrIndex = 0; + int VertIndex = 0; + + for (auto it = OffMeshConnections.begin(); it != OffMeshConnections.end(); it++) + { + OffMeshVerts[VertIndex++] = it->spos[0]; + OffMeshVerts[VertIndex++] = it->spos[1]; + OffMeshVerts[VertIndex++] = it->spos[2]; + OffMeshVerts[VertIndex++] = it->epos[0]; + OffMeshVerts[VertIndex++] = it->epos[1]; + OffMeshVerts[VertIndex++] = it->epos[2]; + + OffMeshRads[CurrIndex] = it->Rad; + OffMeshDirs[CurrIndex] = it->bBiDir; + OffMeshAreas[CurrIndex] = it->Area; + OffMeshFlags[CurrIndex] = it->Flag; + OffMeshIDs[CurrIndex] = it->UserID; + + CurrIndex++; + } + + NumOffMeshConns = OffMeshConnections.size(); + + bNavDataDirty = false; + } + + void PopulateOffMeshConnectionVector() + { + OffMeshConnections.clear(); + + for (int i = 0; i < NumOffMeshConns; i++) + { + float* v = &OffMeshVerts[i*3*2]; + Vector StartPos = Vector(v[0], -v[2], v[1]); + Vector EndPos = Vector(v[3], -v[5], v[4]); + AddOffMeshConnectionDef(StartPos, EndPos, OffMeshAreas[i], OffMeshFlags[i], OffMeshDirs[i], nullptr); + } + + bNavDataDirty = false; + } + + void GetOffMeshConnectionPoints(int UserID, Vector& OutStartLoc, Vector& OutEndLoc) { OutStartLoc = ZERO_VECTOR; OutEndLoc = ZERO_VECTOR; - if (Index > -1 && Index < MAX_OFFMESH_CONNS) + for (auto it = OffMeshConnections.begin(); it != OffMeshConnections.end(); it++) { - float* src = &OffMeshVerts[Index * 3 * 2]; + if (it->UserID == UserID) + { + OutStartLoc.x = it->spos[0]; + OutStartLoc.y = -it->spos[2]; + OutStartLoc.z = it->spos[1]; - OutStartLoc.x = src[0]; - OutStartLoc.y = -src[2]; - OutStartLoc.z = src[1]; + OutEndLoc.x = it->epos[0]; + OutEndLoc.y = -it->epos[2]; + OutEndLoc.z = it->epos[1]; - OutEndLoc.x = src[3]; - OutEndLoc.y = -src[5]; - OutEndLoc.z = src[4]; - } + return; + } + } } void DrawAllConnections(float DrawTime) @@ -260,12 +323,12 @@ struct MeshProcess : public dtTileCacheMeshProcess Vector StartLine = ZERO_VECTOR; Vector EndLine = ZERO_VECTOR; - for (int i = 0; i < NumOffMeshConns; i++) + for (auto it = OffMeshConnections.begin(); it != OffMeshConnections.end(); it++) { - Vector StartLine = Vector(OffMeshVerts[i * 6], -OffMeshVerts[(i * 6) + 2], OffMeshVerts[(i * 6) + 1]); - Vector EndLine = Vector(OffMeshVerts[(i * 6) + 3], -OffMeshVerts[(i * 6) + 5], OffMeshVerts[(i * 6) + 4]); + Vector StartLine = Vector(it->spos[0], -it->spos[2], it->spos[1]); + Vector EndLine = Vector(it->epos[0], -it->epos[2], it->epos[1]); - switch (OffMeshFlags[i]) + switch (it->Flag) { case SAMPLE_POLYFLAGS_WALK: UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 255, 255, 255); @@ -282,7 +345,8 @@ struct MeshProcess : public dtTileCacheMeshProcess case SAMPLE_POLYFLAGS_LADDER: UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 0, 0, 255); break; - case SAMPLE_POLYFLAGS_PHASEGATE: + case SAMPLE_POLYFLAGS_TEAM1PHASEGATE: + case SAMPLE_POLYFLAGS_TEAM2PHASEGATE: UTIL_DrawLine(INDEXENT(1), StartLine, EndLine, DrawTime, 255, 128, 128); break; default: @@ -345,6 +409,11 @@ struct MeshProcess : public dtTileCacheMeshProcess } } + if (bNavDataDirty) + { + UpdateOffMeshData(); + } + params->offMeshConAreas = OffMeshAreas; params->offMeshConCount = NumOffMeshConns; params->offMeshConDir = OffMeshDirs; @@ -800,6 +869,8 @@ bool LoadNavMesh(const char* mapname) fseek(savedFile, header.OffMeshConVertsOffset, SEEK_SET); ReadResult = fread(m_tmproc->OffMeshVerts, header.OffMeshConVertsLength, 1, savedFile); + m_tmproc->PopulateOffMeshConnectionVector(); + // TODO: Need to pass all off mesh connection verts, areas, flags etc as arrays to m_tmproc. Needs to be exported from recast as such status = NavMeshes[REGULAR_NAV_MESH].tileCache->init(&header.regularCacheParams, m_talloc, m_tcomp, m_tmproc); @@ -1015,7 +1086,8 @@ void UTIL_PopulateBaseNavProfiles() BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_BLOCKED, 1.0f); BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_FALLDAMAGE, 1.0f); BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_WALLCLIMB, 1.0f); - BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_PHASEGATE); + BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE); + BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2PHASEGATE); BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_DUCKJUMP); BaseNavProfiles[SKULK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WELD); @@ -1030,7 +1102,8 @@ void UTIL_PopulateBaseNavProfiles() BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setIncludeFlags(0xFFFF); BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.setExcludeFlags(0); BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WALLCLIMB); - BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_PHASEGATE); + BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE); + BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2PHASEGATE); BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_DUCKJUMP); BaseNavProfiles[GORGE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WELD); @@ -1045,7 +1118,8 @@ void UTIL_PopulateBaseNavProfiles() BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_WALLCLIMB, 1.0f); BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setIncludeFlags(0xFFFF); BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.setExcludeFlags(0); - BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_PHASEGATE); + BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE); + BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2PHASEGATE); BaseNavProfiles[LERK_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WELD); BaseNavProfiles[FADE_BASE_NAV_PROFILE].NavMeshIndex = REGULAR_NAV_MESH; @@ -1059,7 +1133,8 @@ void UTIL_PopulateBaseNavProfiles() BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setAreaCost(SAMPLE_POLYAREA_WALLCLIMB, 1.0f); BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setIncludeFlags(0xFFFF); BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.setExcludeFlags(0); - BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_PHASEGATE); + BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE); + BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2PHASEGATE); BaseNavProfiles[FADE_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WELD); BaseNavProfiles[ONOS_BASE_NAV_PROFILE].NavMeshIndex = ONOS_NAV_MESH; @@ -1073,7 +1148,8 @@ void UTIL_PopulateBaseNavProfiles() BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setIncludeFlags(0xFFFF); BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.setExcludeFlags(0); BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WALLCLIMB); - BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_PHASEGATE); + BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM1PHASEGATE); + BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_TEAM2PHASEGATE); BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_WELD); BaseNavProfiles[ONOS_BASE_NAV_PROFILE].Filters.addExcludeFlags(SAMPLE_POLYFLAGS_NOONOS); @@ -1318,85 +1394,6 @@ static float frand() return (float)rand() / (float)RAND_MAX; } -dtStatus FindPhaseGatePathToPoint(const nav_profile& NavProfile, Vector FromLocation, Vector ToLocation, bot_path_node* path, int* pathSize, float MaxAcceptableDistance) -{ - *pathSize = 0; - - const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(NavProfile); - const dtNavMesh* m_navMesh = UTIL_GetNavMeshForProfile(NavProfile); - const dtQueryFilter* m_navFilter = &NavProfile.Filters; - - if (!m_navQuery || vIsZero(FromLocation) || vIsZero(ToLocation)) - { - return DT_FAILURE; - } - - bot_path_node PathToPhaseStart[MAX_AI_PATH_SIZE]; - memset(PathToPhaseStart, 0, sizeof(PathToPhaseStart)); - int PhaseStartPathSize = 0; - - bot_path_node PathToFinalDestination[MAX_AI_PATH_SIZE]; - memset(PathToFinalDestination, 0, sizeof(PathToFinalDestination)); - int PhaseEndPathSize = 0; - - DeployableSearchFilter PhaseGateSearch; - PhaseGateSearch.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; - PhaseGateSearch.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; - PhaseGateSearch.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; - PhaseGateSearch.ReachabilityFlags = AI_REACHABILITY_MARINE; - - AvHAIBuildableStructure* StartPhaseGate = AITAC_FindClosestDeployableToLocation(FromLocation, &PhaseGateSearch); - AvHAIBuildableStructure* EndPhaseGate = AITAC_FindClosestDeployableToLocation(ToLocation, &PhaseGateSearch); - - if (!StartPhaseGate || !EndPhaseGate || (StartPhaseGate == EndPhaseGate)) { return DT_FAILURE; } - - float TotalDist = vDist2DSq(FromLocation, StartPhaseGate->edict->v.origin) + vDist2DSq(EndPhaseGate->edict->v.origin, ToLocation); - - if (TotalDist > vDist2DSq(FromLocation, ToLocation)) { return DT_FAILURE; } - - dtStatus RouteToFirstPhaseGate = FindPathClosestToPoint(NavProfile, FromLocation, StartPhaseGate->edict->v.origin, PathToPhaseStart, &PhaseStartPathSize, max_ai_use_reach); - - if (dtStatusFailed(RouteToFirstPhaseGate)) - { - return DT_FAILURE; - } - - dtStatus RouteToFinalPoint = FindPathClosestToPoint(NavProfile, EndPhaseGate->edict->v.origin, ToLocation, PathToFinalDestination, &PhaseEndPathSize, MaxAcceptableDistance); - - if (dtStatusFailed(RouteToFinalPoint)) - { - return DT_FAILURE; - } - - // Now we join together the path to the starting phase gate and the path from the phase destination to the end, and add the phase itself in the middle - - int CurrPathIndex = 0; - - for (int i = 0; i < PhaseStartPathSize; i++) - { - memcpy(&path[CurrPathIndex++], &PathToPhaseStart[i], sizeof(bot_path_node)); - } - - // Add a node to inform the bot they have to use the phase gate - path[CurrPathIndex].Location = EndPhaseGate->edict->v.origin + Vector(0.0f, 0.0f, 10.0f); - path[CurrPathIndex].area = SAMPLE_POLYAREA_GROUND; - path[CurrPathIndex].flag = SAMPLE_POLYFLAGS_PHASEGATE; - path[CurrPathIndex].poly = UTIL_GetNearestPolyRefForEntity(EndPhaseGate->edict); - path[CurrPathIndex].requiredZ = EndPhaseGate->edict->v.origin.z; - - CurrPathIndex++; - - // Append the path from the destination phase to the end - for (int i = 1; i < PhaseEndPathSize; i++) - { - memcpy(&path[CurrPathIndex++], &PathToFinalDestination[i], sizeof(bot_path_node)); - } - - *pathSize = CurrPathIndex; - - return DT_SUCCESS; -} - // Special path finding that takes flight movement into account dtStatus FindFlightPathToPoint(const nav_profile &NavProfile, Vector FromLocation, Vector ToLocation, bot_path_node* path, int* pathSize, float MaxAcceptableDistance) { @@ -1836,22 +1833,6 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, return DT_FAILURE; } - DeployableSearchFilter PGFilter; - PGFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; - PGFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; - PGFilter.bConsiderPhaseDistance = false; - - if (IsPlayerMarine(pBot->Edict) && AITAC_GetNumDeployablesNearLocation(ZERO_VECTOR, &PGFilter) > 1) - { - dtStatus PhaseStatus = FindPhaseGatePathToPoint(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, ToLocation, path, pathSize, MaxAcceptableDistance); - - if (dtStatusSucceed(PhaseStatus)) - { - pBot->BotNavInfo.CurrentPathPoint = 1; - return DT_SUCCESS; - } - } - float pStartPos[3] = { FromLocation.x, FromLocation.z, -FromLocation.y }; float pEndPos[3] = { ToLocation.x, ToLocation.z, -ToLocation.y }; @@ -2085,11 +2066,11 @@ bool HasBotReachedPathPoint(const AvHAIPlayer* pBot) SamplePolyFlags CurrentNavFlag = (SamplePolyFlags)pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].flag; Vector CurrentMoveDest = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].Location; - Vector PrevMoveDest = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint - 1].Location; + Vector PrevMoveDest = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].FromLocation; bool bIsAtFinalPathPoint = (pBot->BotNavInfo.CurrentPathPoint == (pBot->BotNavInfo.PathSize - 1)); - Vector ClosestPointToPath = vClosestPointOnLine2D(pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint - 1].Location, pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].Location, pEdict->v.origin); + Vector ClosestPointToPath = vClosestPointOnLine2D(PrevMoveDest, CurrentMoveDest, pEdict->v.origin); bool bDestIsDirectlyReachable = UTIL_PointIsDirectlyReachable(CurrentPos, CurrentMoveDest); bool bAtOrPastDestination = vEquals2D(ClosestPointToPath, CurrentMoveDest, 1.0f) && bDestIsDirectlyReachable; @@ -2145,6 +2126,9 @@ bool HasBotReachedPathPoint(const AvHAIPlayer* pBot) { return (fabs(pBot->CollisionHullBottomLocation.z - CurrentMoveDest.z) < 50.0f); } + case SAMPLE_POLYFLAGS_TEAM1PHASEGATE: + case SAMPLE_POLYFLAGS_TEAM2PHASEGATE: + return (vDist2DSq(pBot->CurrentFloorPosition, CurrentMoveDest) < sqrf(32.0f)); default: return (bAtOrPastDestination && UTIL_QuickTrace(pEdict, pEdict->v.origin, CurrentMoveDest)); } @@ -2961,7 +2945,8 @@ void NewMove(AvHAIPlayer* pBot) } } break; - case SAMPLE_POLYFLAGS_PHASEGATE: + case SAMPLE_POLYFLAGS_TEAM1PHASEGATE: + case SAMPLE_POLYFLAGS_TEAM2PHASEGATE: PhaseGateMove(pBot, MoveFrom, MoveTo); break; default: @@ -3583,7 +3568,7 @@ bool IsBotOffPath(const AvHAIPlayer* pBot) // If we're trying to use a phase gate, then we're fine as long as there is a phase gate within reach at the start and end teleport points - if (pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].flag == SAMPLE_POLYFLAGS_PHASEGATE) + if (pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].flag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint].flag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE) { DeployableSearchFilter PGFilter; PGFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; @@ -4705,7 +4690,7 @@ bool AbortCurrentMove(AvHAIPlayer* pBot, const Vector NewDestination) } } - if (flag == SAMPLE_POLYFLAGS_PHASEGATE) + if (flag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || flag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE) { return true; } @@ -4842,6 +4827,17 @@ void MarineUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle) } } + SamplePolyFlags ExcludePhaseGateFlag = (pBot->Player->GetTeam() == GetGameRules()->GetTeamANumber()) ? SAMPLE_POLYFLAGS_TEAM2PHASEGATE : SAMPLE_POLYFLAGS_TEAM1PHASEGATE; + SamplePolyFlags IncludePhaseGateFlag = (ExcludePhaseGateFlag & SAMPLE_POLYFLAGS_TEAM1PHASEGATE) ? SAMPLE_POLYFLAGS_TEAM2PHASEGATE : SAMPLE_POLYFLAGS_TEAM1PHASEGATE; + + if (!(NavProfile->Filters.getExcludeFlags() & ExcludePhaseGateFlag)) + { + pBot->BotNavInfo.bNavProfileChanged = true; + + NavProfile->Filters.removeExcludeFlags(IncludePhaseGateFlag); + NavProfile->Filters.addExcludeFlags(ExcludePhaseGateFlag); + } + if (MoveStyle == pBot->BotNavInfo.PreviousMoveStyle) { return; } pBot->BotNavInfo.PreviousMoveStyle = MoveStyle; @@ -5517,7 +5513,7 @@ void BotFollowPath(AvHAIPlayer* pBot) Vector TargetMoveLocation = BotNavInfo->CurrentPath[BotNavInfo->CurrentPathPoint].Location; - bool bIsUsingPhaseGate = (BotNavInfo->CurrentPath[BotNavInfo->CurrentPathPoint].flag == SAMPLE_POLYFLAGS_PHASEGATE); + bool bIsUsingPhaseGate = (BotNavInfo->CurrentPath[BotNavInfo->CurrentPathPoint].flag == SAMPLE_POLYFLAGS_TEAM1PHASEGATE || BotNavInfo->CurrentPath[BotNavInfo->CurrentPathPoint].flag == SAMPLE_POLYFLAGS_TEAM2PHASEGATE); bool bIsJumping = (BotNavInfo->CurrentPath[BotNavInfo->CurrentPathPoint].flag == SAMPLE_POLYFLAGS_JUMP); @@ -7175,35 +7171,61 @@ unsigned char UTIL_GetNextBotCurrentPathArea(AvHAIPlayer* pBot) return pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint + 1].area; } -int UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned char flags, bool bBiDirectional) +void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned short flags, bool bBiDirectional, AvHAIOffMeshConnection* NewConnectionDef) { + Vector ConnStart, ConnEnd; + + TraceResult hit; + UTIL_TraceLine(StartLoc + Vector(0.0f, 0.0f, 5.0f), StartLoc - Vector(0.0f, 0.0f, 100.0f), ignore_monsters, ignore_glass, nullptr, &hit); + + ConnStart = (hit.flFraction < 1.0f) ? hit.vecEndPos : StartLoc; + + UTIL_TraceLine(EndLoc + Vector(0.0f, 0.0f, 5.0f), EndLoc - Vector(0.0f, 0.0f, 100.0f), ignore_monsters, ignore_glass, nullptr, &hit); + + ConnEnd = (hit.flFraction < 1.0f) ? hit.vecEndPos : EndLoc; + + bool bMeshModified = false; + if (NavMeshes[REGULAR_NAV_MESH].tileCache) { + NewConnectionDef->MeshConnectionIndex = -1; MeshProcess* m_tmproc = (MeshProcess*)NavMeshes[REGULAR_NAV_MESH].tileCache->getMeshProcess(); if (m_tmproc) { - return m_tmproc->AddOffMeshConnectionDef(StartLoc, EndLoc, area, flags, bBiDirectional); - UTIL_OnOffMeshConnectionModified(StartLoc, EndLoc); + m_tmproc->AddOffMeshConnectionDef(ConnStart, ConnEnd, area, flags, bBiDirectional, NewConnectionDef); + if (NewConnectionDef->MeshConnectionIndex > -1) { bMeshModified = true; } } } + + if (bMeshModified) + { + UTIL_OnOffMeshConnectionModified(ConnStart, ConnEnd); + } + } -void UTIL_RemoveOffMeshConnection(int ConnectionIndex) +void UTIL_RemoveOffMeshConnections(AvHAIOffMeshConnection* NewConnectionDef) { - Vector StartLoc, EndLoc; + if (NewConnectionDef->MeshConnectionIndex < 0) { return; } + Vector StartLoc, EndLoc; + if (NavMeshes[REGULAR_NAV_MESH].tileCache) { MeshProcess* m_tmproc = (MeshProcess*)NavMeshes[REGULAR_NAV_MESH].tileCache->getMeshProcess(); if (m_tmproc) { - m_tmproc->GetOffMeshConnectionPoints(ConnectionIndex, StartLoc, EndLoc); - m_tmproc->RemoveOffMeshConnectionDef(ConnectionIndex); + m_tmproc->GetOffMeshConnectionPoints(NewConnectionDef->MeshConnectionIndex, StartLoc, EndLoc); + m_tmproc->RemoveOffMeshConnectionDef(NewConnectionDef->MeshConnectionIndex); + NewConnectionDef->MeshConnectionIndex = -1; } + } + NewConnectionDef->MeshConnectionIndex = -1; + UTIL_OnOffMeshConnectionModified(StartLoc, EndLoc); } diff --git a/main/source/mod/AIPlayers/AvHAINavigation.h b/main/source/mod/AIPlayers/AvHAINavigation.h index 3f256869..f80a38e7 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.h +++ b/main/source/mod/AIPlayers/AvHAINavigation.h @@ -56,11 +56,12 @@ enum SamplePolyFlags SAMPLE_POLYFLAGS_JUMP = 1 << 5, // Requires a regular jump to traverse SAMPLE_POLYFLAGS_DUCKJUMP = 1 << 6, // Requires a duck-jump to traverse SAMPLE_POLYFLAGS_NOONOS = 1 << 7, // This movement is not allowed by onos - SAMPLE_POLYFLAGS_PHASEGATE = 1 << 8, // Requires using a phase gate to traverse - SAMPLE_POLYFLAGS_TEAM1STRUCTURE = 1 << 9, // A team 1 structure is in the way that cannot be jumped over. Impassable to team 1 players - SAMPLE_POLYFLAGS_TEAM2STRUCTURE = 1 << 10, // A team 2 structure is in the way that cannot be jumped over. Impassable to team 2 players - SAMPLE_POLYFLAGS_WELD = 1 << 11, // Requires a welder to get through here - SAMPLE_POLYFLAGS_DOOR = 1 << 12, // Requires a welder to get through here + SAMPLE_POLYFLAGS_TEAM1PHASEGATE = 1 << 8, // Requires using a phase gate to traverse (team 1 only) + SAMPLE_POLYFLAGS_TEAM2PHASEGATE = 1 << 9, // Requires using a phase gate to traverse (team 2 only) + SAMPLE_POLYFLAGS_TEAM1STRUCTURE = 1 << 10, // A team 1 structure is in the way that cannot be jumped over. Impassable to team 1 players (assume cannot teamkill own structures) + SAMPLE_POLYFLAGS_TEAM2STRUCTURE = 1 << 11, // A team 2 structure is in the way that cannot be jumped over. Impassable to team 2 players (assume cannot teamkill own structures) + SAMPLE_POLYFLAGS_WELD = 1 << 12, // Requires a welder to get through here + SAMPLE_POLYFLAGS_DOOR = 1 << 13, // Requires a welder to get through here SAMPLE_POLYFLAGS_DISABLED = 1 << 15, // Disabled, not usable by anyone SAMPLE_POLYFLAGS_ALL = 0xffff // All abilities. @@ -92,7 +93,7 @@ typedef struct _NAV_DOOR { CBaseToggle* DoorEntity = nullptr; edict_t* DoorEdict = nullptr; // Reference to the func_door - unsigned int ObstacleRefs[32][8]; // Dynamic obstacle ref. Used to add/remove the obstacle as the door is opened/closed + unsigned int ObstacleRefs[32][MAX_NAV_MESHES]; // Dynamic obstacle ref. Used to add/remove the obstacle as the door is opened/closed int NumObstacles = 0; vector TriggerEnts; // Reference to the trigger edicts (e.g. func_trigger, func_button etc.) DoorActivationType ActivationType = DOOR_NONE; // How the door should be opened @@ -103,7 +104,7 @@ typedef struct _NAV_DOOR typedef struct _NAV_WELDABLE { edict_t* WeldableEdict = nullptr; - unsigned int ObstacleRefs[32][8]; + unsigned int ObstacleRefs[32][MAX_NAV_MESHES]; int NumObstacles = 0; } nav_weldable; @@ -133,7 +134,6 @@ static const int TILECACHESET_VERSION = 1; static const float pExtents[3] = { 400.0f, 50.0f, 400.0f }; // Default extents (in GoldSrc units) to find the nearest spot on the nav mesh static const float pReachableExtents[3] = { max_ai_use_reach, max_ai_use_reach, max_ai_use_reach }; // Extents (in GoldSrc units) to determine if something is on the nav mesh -static const int MAX_NAV_MESHES = 8; // Max number of nav meshes allowed. Currently 3 are used (one for building placement, one for the onos, and a regular one for everyone else) static const int MAX_NAV_PROFILES = 16; // Max number of possible nav profiles. Currently 9 are used (see top of this header file) static const int REGULAR_NAV_MESH = 0; @@ -293,8 +293,8 @@ void UTIL_RemoveTemporaryObstacle(unsigned int ObstacleRef); void UTIL_RemoveTemporaryObstacles(unsigned int* ObstacleRefs); -int UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned char flags, bool bBiDirectional); -void UTIL_RemoveOffMeshConnection(int ConnectionIndex); +void UTIL_AddOffMeshConnection(Vector StartLoc, Vector EndLoc, unsigned char area, unsigned short flags, bool bBiDirectional, AvHAIOffMeshConnection* NewConnectionDef); +void UTIL_RemoveOffMeshConnections(AvHAIOffMeshConnection* NewConnectionDef); void UTIL_OnOffMeshConnectionModified(Vector StartLoc, Vector EndLoc); @@ -332,9 +332,6 @@ void MoveDirectlyTo(AvHAIPlayer* pBot, const Vector Destination); // Check if there are any players in our way and try to move around them. If we can't, then back up to let them through void HandlePlayerAvoidance(AvHAIPlayer* pBot, const Vector MoveDestination); -// Special path finding that takes the presence of phase gates into account -dtStatus FindPhaseGatePathToPoint(const nav_profile& NavProfile, Vector FromLocation, Vector ToLocation, bot_path_node* path, int* pathSize, float MaxAcceptableDistance); - // Special path finding that takes the presence of phase gates into account dtStatus FindFlightPathToPoint(const nav_profile& NavProfile, Vector FromLocation, Vector ToLocation, bot_path_node* path, int* pathSize, float MaxAcceptableDistance); diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp index add4f3c4..ab17eeba 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp @@ -29,6 +29,9 @@ float AIStartedTime = 0.0f; // Used to give 5-second grace period before adding extern int m_spriteTexture; +Vector DebugVector1 = ZERO_VECTOR; +Vector DebugVector2 = ZERO_VECTOR; + string BotNames[MAX_PLAYERS] = { "MrRobot", "Wall-E", "BeepBoop", @@ -739,4 +742,4 @@ void AIMGR_UpdateAIMapData() void AIMGR_BotPrecache() { m_spriteTexture = PRECACHE_MODEL("sprites/zbeam6.spr"); -} \ No newline at end of file +} diff --git a/main/source/mod/AIPlayers/AvHAITactical.cpp b/main/source/mod/AIPlayers/AvHAITactical.cpp index 86194409..4c469f4d 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.cpp +++ b/main/source/mod/AIPlayers/AvHAITactical.cpp @@ -52,6 +52,86 @@ extern nav_profile BaseNavProfiles[MAX_NAV_PROFILES]; // Array of nav profiles bool bNavMeshModified = false; +std::vector AITAC_FindAllDeployables(const Vector& Location, const DeployableSearchFilter* Filter) +{ + std::vector Result; + + AvHTeamNumber TeamA = GetGameRules()->GetTeamANumber(); + AvHTeamNumber TeamB = GetGameRules()->GetTeamBNumber(); + + float CurrMinDist = 0.0f; + + float MinDistSq = sqrf(Filter->MinSearchRadius); + float MaxDistSq = sqrf(Filter->MaxSearchRadius); + + bool bUseMinDist = MinDistSq > 0.1f; + bool bUseMaxDist = MaxDistSq > 0.1f; + + if (Filter->DeployableTeam == TeamA || Filter->DeployableTeam == TEAM_IND) + { + for (auto& it : TeamAStructureMap) + { + if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; } + if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; } + + if (Filter->ReachabilityFlags != AI_REACHABILITY_NONE) + { + unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags); + + if (Filter->ReachabilityTeam != TEAM_IND) + { + StructureReachabilityFlags = (Filter->ReachabilityTeam == TeamA) ? it.second.TeamAReachabilityFlags : it.second.TeamBReachabilityFlags; + } + + if (!(StructureReachabilityFlags & Filter->ReachabilityFlags)) { continue; } + } + + if (it.second.StructureType & Filter->DeployableTypes) + { + float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location); + + if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq)) + { + Result.push_back(&it.second); + } + } + } + } + + if (Filter->DeployableTeam == TeamB || Filter->DeployableTeam == TEAM_IND) + { + for (auto& it : TeamBStructureMap) + { + if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; } + if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; } + + if (Filter->ReachabilityFlags != AI_REACHABILITY_NONE) + { + unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags); + + if (Filter->ReachabilityTeam != TEAM_IND) + { + StructureReachabilityFlags = (Filter->ReachabilityTeam == TeamA) ? it.second.TeamAReachabilityFlags : it.second.TeamBReachabilityFlags; + } + + if (!(StructureReachabilityFlags & Filter->ReachabilityFlags)) { continue; } + } + + if (it.second.StructureType & Filter->DeployableTypes) + { + float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location); + + if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq)) + { + Result.push_back(&it.second); + } + } + } + } + + return Result; +} + bool AITAC_DeployableExistsAtLocation(const Vector& Location, const DeployableSearchFilter* Filter) { AvHTeamNumber TeamA = GetGameRules()->GetTeamANumber(); @@ -1019,6 +1099,7 @@ void AITAC_RefreshBuildableStructures() { if (it->second.LastSeen < StructureRefreshFrame) { + AITAC_OnStructureDestroyed(&it->second); UTIL_RemoveTemporaryObstacles(it->second.ObstacleRefs); it = TeamAStructureMap.erase(it); } @@ -1037,6 +1118,7 @@ void AITAC_RefreshBuildableStructures() { if (it->second.LastSeen < StructureRefreshFrame) { + AITAC_OnStructureDestroyed(&it->second); UTIL_RemoveTemporaryObstacles(it->second.ObstacleRefs); it = TeamBStructureMap.erase(it); } @@ -1347,6 +1429,10 @@ void AITAC_UpdateBuildableStructure(CBaseEntity* Structure) BuildingMap[EntIndex].edict = BuildingEdict; BuildingMap[EntIndex].StructureType = StructureType; + BuildingMap[EntIndex].OffMeshConnections.clear(); + + memset(&BuildingMap[EntIndex].ObstacleRefs, 0, sizeof(BuildingMap[EntIndex].ObstacleRefs)); + bool bShouldCollide = UTIL_ShouldStructureCollide(StructureType); if (bShouldCollide) @@ -1372,26 +1458,33 @@ void AITAC_UpdateBuildableStructure(CBaseEntity* Structure) BuildingMap[EntIndex].Location = BaseBuildable->pev->origin; } - BuildingMap[EntIndex].StructureStatusFlags = STRUCTURE_STATUS_NONE; + unsigned int NewFlags = STRUCTURE_STATUS_NONE; if (BaseBuildable->GetIsBuilt()) { - BuildingMap[EntIndex].StructureStatusFlags |= STRUCTURE_STATUS_COMPLETED; + if (!(BuildingMap[EntIndex].StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { + AITAC_OnStructureCompleted(&BuildingMap[EntIndex]); + } + NewFlags |= STRUCTURE_STATUS_COMPLETED; } if (UTIL_IsStructureElectrified(BuildingEdict)) { - BuildingMap[EntIndex].StructureStatusFlags |= STRUCTURE_STATUS_ELECTRIFIED; + NewFlags |= STRUCTURE_STATUS_ELECTRIFIED; } if (BuildingEdict->v.iuser4 & MASK_PARASITED) { - BuildingMap[EntIndex].StructureStatusFlags |= STRUCTURE_STATUS_PARASITED; + NewFlags |= STRUCTURE_STATUS_PARASITED; } if (BaseBuildable->GetIsRecycling()) { - BuildingMap[EntIndex].StructureStatusFlags |= STRUCTURE_STATUS_RECYCLING; + if (!(BuildingMap[EntIndex].StructureStatusFlags & STRUCTURE_STATUS_RECYCLING)) + { + AITAC_OnStructureBeginRecycling(&BuildingMap[EntIndex]); + } + NewFlags |= STRUCTURE_STATUS_RECYCLING; } float NewHealthPercent = (BuildingEdict->v.health / BuildingEdict->v.max_health); @@ -1405,9 +1498,10 @@ void AITAC_UpdateBuildableStructure(CBaseEntity* Structure) if (gpGlobals->time - BuildingMap[EntIndex].lastDamagedTime < 10.0f) { - BuildingMap[EntIndex].StructureStatusFlags |= STRUCTURE_STATUS_UNDERATTACK; + NewFlags |= STRUCTURE_STATUS_UNDERATTACK; } + BuildingMap[EntIndex].StructureStatusFlags = NewFlags; BuildingMap[EntIndex].LastSeen = StructureRefreshFrame; } @@ -1446,6 +1540,105 @@ void AITAC_OnStructureCreated(AvHAIBuildableStructure* NewStructure) } +void AITAC_OnStructureCompleted(AvHAIBuildableStructure* NewStructure) +{ + if (NewStructure->StructureType == STRUCTURE_MARINE_PHASEGATE) + { + DeployableSearchFilter Filter; + Filter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; + Filter.DeployableTeam = (AvHTeamNumber)NewStructure->edict->v.team; + Filter.ReachabilityFlags = AI_REACHABILITY_NONE; + Filter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + + // Get all other completed phase gates for this team and add bidirectional connections to them + std::vector OtherPhaseGates = AITAC_FindAllDeployables(NewStructure->Location, &Filter); + + SamplePolyFlags NewFlag = ((AvHTeamNumber)NewStructure->edict->v.team == GetGameRules()->GetTeamANumber()) ? SAMPLE_POLYFLAGS_TEAM1PHASEGATE : SAMPLE_POLYFLAGS_TEAM2PHASEGATE; + + for (auto pg = OtherPhaseGates.begin(); pg != OtherPhaseGates.end(); pg++) + { + // Don't add off-mesh connections to ourselves! + if ((*pg) == NewStructure) { continue; } + + AvHAIBuildableStructure* OtherPhaseGate = (*pg); + + AvHAIOffMeshConnection NewConnection; + NewConnection.FromLocation = NewStructure->Location; + NewConnection.ToLocation = OtherPhaseGate->Location; + NewConnection.ConnectionFlags = NewFlag; + NewConnection.TargetObject = OtherPhaseGate->edict; + NewConnection.MeshConnectionIndex = -1; + + UTIL_AddOffMeshConnection(NewStructure->Location, OtherPhaseGate->Location, SAMPLE_POLYAREA_GROUND, NewFlag, true, &NewConnection); + + NewStructure->OffMeshConnections.push_back(NewConnection); + + } + } +} + +void AITAC_RemovePhaseGateConnections(AvHAIBuildableStructure* SourceGate, AvHAIBuildableStructure* TargetGate) +{ + if (!SourceGate || !TargetGate) { return; } + + for (auto it = SourceGate->OffMeshConnections.begin(); it != SourceGate->OffMeshConnections.end();) + { + if (it->TargetObject == TargetGate->edict) + { + UTIL_RemoveOffMeshConnections(&(*it)); + it = SourceGate->OffMeshConnections.erase(it); + } + else + { + it++; + } + } +} + +void AITAC_OnStructureBeginRecycling(AvHAIBuildableStructure* RecyclingStructure) +{ + // For phase gates, treat them like they've been destroyed + if (RecyclingStructure->StructureType == STRUCTURE_MARINE_PHASEGATE) + { + AITAC_OnStructureDestroyed(RecyclingStructure); + } +} + +void AITAC_OnStructureDestroyed(AvHAIBuildableStructure* DestroyedStructure) +{ + if (DestroyedStructure->StructureType == STRUCTURE_MARINE_PHASEGATE) + { + // Eliminate all connections from this phase gate + for (auto it = DestroyedStructure->OffMeshConnections.begin(); it != DestroyedStructure->OffMeshConnections.begin();) + { + UTIL_RemoveOffMeshConnections(&(*it)); + + it = DestroyedStructure->OffMeshConnections.erase(it); + } + + DestroyedStructure->OffMeshConnections.clear(); + + DeployableSearchFilter Filter; + Filter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; + Filter.DeployableTeam = (AvHTeamNumber)DestroyedStructure->edict->v.team; + Filter.ReachabilityFlags = AI_REACHABILITY_NONE; + Filter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + + // Get all other completed phase gates for this team and remove any connections going to this structure + std::vector OtherPhaseGates = AITAC_FindAllDeployables(DestroyedStructure->Location, &Filter); + + for (auto it = OtherPhaseGates.begin(); it != OtherPhaseGates.end(); it++) + { + // Don't check for off-mesh connections from ourselves! + if ((*it) == DestroyedStructure) { continue; } + + AvHAIBuildableStructure* OtherPhaseGate = (*it); + + AITAC_RemovePhaseGateConnections(OtherPhaseGate, DestroyedStructure); + } + } +} + void AITAC_LinkAlienStructureToTask(AvHAIPlayer* pBot, AvHAIBuildableStructure* NewStructure) { diff --git a/main/source/mod/AIPlayers/AvHAITactical.h b/main/source/mod/AIPlayers/AvHAITactical.h index db3c6236..6217318b 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.h +++ b/main/source/mod/AIPlayers/AvHAITactical.h @@ -21,6 +21,7 @@ static const float structure_inventory_refresh_rate = 0.2f; static const float item_inventory_refresh_rate = 0.1f; bool AITAC_DeployableExistsAtLocation(const Vector& Location, const DeployableSearchFilter* Filter); +std::vector AITAC_FindAllDeployables(const Vector& Location, const DeployableSearchFilter* Filter); AvHAIBuildableStructure* AITAC_FindClosestDeployableToLocation(const Vector& Location, const DeployableSearchFilter* Filter); AvHAIBuildableStructure* AITAC_GetDeployableRefFromEdict(const edict_t* Structure); AvHAIBuildableStructure* AITAC_GetNearestDeployableDirectlyReachable(AvHAIPlayer* pBot, const Vector Location, const DeployableSearchFilter* Filter); @@ -34,6 +35,9 @@ void AITAC_RefreshReachabilityForStructure(AvHAIBuildableStructure* Structu void AITAC_RefreshReachabilityForResNode(AvHAIResourceNode* ResNode); void AITAC_RefreshReachabilityForItem(AvHAIDroppedItem* Item); void AITAC_OnStructureCreated(AvHAIBuildableStructure* NewStructure); +void AITAC_OnStructureCompleted(AvHAIBuildableStructure* NewStructure); +void AITAC_OnStructureBeginRecycling(AvHAIBuildableStructure* RecyclingStructure); +void AITAC_OnStructureDestroyed(AvHAIBuildableStructure* DestroyedStructure); void AITAC_LinkDeployedItemToAction(AvHAIPlayer* CommanderBot, const AvHAIDroppedItem* NewItem); void AITAC_LinkAlienStructureToTask(AvHAIPlayer* pBot, AvHAIBuildableStructure* NewStructure); diff --git a/main/source/mod/AvHConsoleCommands.cpp b/main/source/mod/AvHConsoleCommands.cpp index efc5d7c4..29eea07b 100644 --- a/main/source/mod/AvHConsoleCommands.cpp +++ b/main/source/mod/AvHConsoleCommands.cpp @@ -1414,6 +1414,12 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) theSuccess = true; } } + else if (FStrEq(pcmd, "drawoffmesh")) + { + AIDEBUG_DrawOffMeshConnections(10.0f); + + theSuccess = true; + } else if (FStrEq(pcmd, "tracedoor")) { Vector TraceStart = GetPlayerEyePosition(theAvHPlayer->edict()); // origin + pev->view_ofs