diff --git a/main/navmeshes/co_kestrel.nav b/main/navmeshes/co_kestrel.nav index d9f02569..bd5ed480 100644 Binary files a/main/navmeshes/co_kestrel.nav and b/main/navmeshes/co_kestrel.nav differ diff --git a/main/navmeshes/ns_askr_b6.nav b/main/navmeshes/ns_askr_b6.nav index 87f28314..6763fd2e 100644 Binary files a/main/navmeshes/ns_askr_b6.nav and b/main/navmeshes/ns_askr_b6.nav differ diff --git a/main/navmeshes/ns_lost.nav b/main/navmeshes/ns_lost.nav index 54be67ac..378f2586 100644 Binary files a/main/navmeshes/ns_lost.nav and b/main/navmeshes/ns_lost.nav differ diff --git a/main/navmeshes/ns_machina.nav b/main/navmeshes/ns_machina.nav index 3e0fdb9f..831f4a2c 100644 Binary files a/main/navmeshes/ns_machina.nav and b/main/navmeshes/ns_machina.nav differ diff --git a/main/navmeshes/ns_nothing.nav b/main/navmeshes/ns_nothing.nav index a307e35c..a0c01e9b 100644 Binary files a/main/navmeshes/ns_nothing.nav and b/main/navmeshes/ns_nothing.nav differ diff --git a/main/source/mod/AvHAIConstants.h b/main/source/mod/AvHAIConstants.h index 5eec3bf8..00946ad8 100644 --- a/main/source/mod/AvHAIConstants.h +++ b/main/source/mod/AvHAIConstants.h @@ -228,6 +228,7 @@ typedef struct _OFF_MESH_CONN Vector FromLocation = g_vecZero; Vector ToLocation = g_vecZero; edict_t* TargetObject = nullptr; + bool bBiDirectional = false; } AvHAIOffMeshConnection; typedef struct _STRUCTURE_OBSTACLE @@ -749,11 +750,11 @@ typedef struct AVH_AI_PLAYER vector DangerTurrets; - AvHAIPlayerTask* CurrentTask = nullptr; // Bot's current task they're performing AvHAIPlayerTask PrimaryBotTask; AvHAIPlayerTask SecondaryBotTask; AvHAIPlayerTask WantsAndNeedsTask; AvHAIPlayerTask CommanderTask; // Task assigned by the commander + AvHAIPlayerTask* CurrentTask = &PrimaryBotTask; // Bot's current task they're performing float BotNextTaskEvaluationTime = 0.0f; diff --git a/main/source/mod/AvHAINavigation.cpp b/main/source/mod/AvHAINavigation.cpp index 3b55d255..ac8e2594 100644 --- a/main/source/mod/AvHAINavigation.cpp +++ b/main/source/mod/AvHAINavigation.cpp @@ -1091,6 +1091,7 @@ bool LoadNavMesh(const char* mapname) NewMapConnection.TargetObject = nullptr; NewMapConnection.FromLocation = Vector(def.pos[0], -def.pos[2], def.pos[1]); NewMapConnection.ToLocation = Vector(def.pos[3], -def.pos[5], def.pos[4]); + NewMapConnection.bBiDirectional = def.bBiDir; for (int ii = 0; ii < BUILDING_NAV_MESH; ii++) { @@ -1680,7 +1681,7 @@ dtStatus FindFlightPathToPoint(const nav_profile &NavProfile, Vector FromLocatio for (int nVert = 0; nVert < nVertCount; nVert++) { Vector NextPathPoint = g_vecZero; - Vector PrevPoint = (path.size() > 0) ? path.back().Location : FromFloorLocation; + Vector PrevPoint = (path.size() > 0) ? path.back().Location : FromLocation; // The path point output by Detour uses the OpenGL, right-handed coordinate system. Convert to Goldsrc coordinates NextPathPoint.x = StraightPath[nIndex++]; @@ -1992,13 +1993,55 @@ dtStatus FindPathClosestToPoint(const nav_profile& NavProfile, const Vector From return DT_SUCCESS; } -dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, const Vector FromLocation, const Vector ToLocation, vector& path, float MaxAcceptableDistance) +Vector NAV_GetNearestLiftDisembarkPoint(nav_door* LiftReference) +{ + AvHAIOffMeshConnection* NearestConnection = UTIL_GetOffMeshConnectionForLift(LiftReference); + + if (!NearestConnection) { return ZERO_VECTOR; } + + Vector DesiredStartStop = ZERO_VECTOR; + Vector DesiredEndStop = ZERO_VECTOR; + float minStartDist = 0.0f; + float minEndDist = 0.0f; + + // Find the desired stop point for us to get onto the lift + for (auto it = LiftReference->StopPoints.begin(); it != LiftReference->StopPoints.end(); it++) + { + Vector LiftStopPoint = (*it) + Vector(0.0f, 0.0f, LiftReference->DoorEdict->v.size.z * 0.5f); + + float thisStartDist = vDist3DSq(LiftStopPoint, NearestConnection->FromLocation); + float thisEndDist = vDist3DSq(LiftStopPoint, NearestConnection->ToLocation); + if (vIsZero(DesiredStartStop) || thisStartDist < minStartDist) + { + DesiredStartStop = *it; + minStartDist = thisStartDist; + } + + if (vIsZero(DesiredEndStop) || thisEndDist < minEndDist) + { + DesiredEndStop = *it; + minEndDist = thisEndDist; + } + } + + Vector LiftPosition = UTIL_GetCentreOfEntity(LiftReference->DoorEdict); + + bool bIsLiftMoving = (LiftReference->DoorEdict->v.velocity.Length() > 0.0f); + bool bIsLiftMovingToStart = bIsLiftMoving && (vDist3DSq(LiftReference->DoorEntity->m_vecFinalDest, DesiredStartStop) < sqrf(50.0f)); + bool bIsLiftMovingToEnd = bIsLiftMoving && (vDist3DSq(LiftReference->DoorEntity->m_vecFinalDest, DesiredEndStop) < sqrf(50.0f)); + bool bIsLiftAtOrNearStart = (vDist3DSq(LiftPosition, DesiredStartStop) < sqrf(50.0f)); + bool bIsLiftAtOrNearEnd = (vDist3DSq(LiftPosition, DesiredEndStop) < sqrf(50.0f)); + + return (bIsLiftAtOrNearStart || bIsLiftMovingToStart) ? NearestConnection->FromLocation : NearestConnection->ToLocation; +} + +dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, const Vector ToLocation, vector& path, float MaxAcceptableDistance) { if (!pBot) { return DT_FAILURE; } if (pBot->BotNavInfo.NavProfile.bFlyingProfile) { - return FindFlightPathToPoint(pBot->BotNavInfo.NavProfile, FromLocation, ToLocation, path, MaxAcceptableDistance); + return FindFlightPathToPoint(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, ToLocation, path, MaxAcceptableDistance); } const dtNavMeshQuery* m_navQuery = UTIL_GetNavMeshQueryForProfile(pBot->BotNavInfo.NavProfile); @@ -2007,12 +2050,39 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, bool bHasWelder = (m_navFilter->getIncludeFlags() & SAMPLE_POLYFLAGS_WELD); - if (!m_navQuery || !m_navMesh || !m_navFilter || vIsZero(FromLocation) || vIsZero(ToLocation)) + if (!m_navQuery || !m_navMesh || !m_navFilter || vIsZero(ToLocation)) { return DT_FAILURE; } + Vector FromLocation = pBot->CurrentFloorPosition; + Vector FromFloorLocation = AdjustPointForPathfinding(FromLocation); + + nav_door* LiftReference = UTIL_GetNavDoorByEdict(pBot->Edict->v.groundentity); + bool bMustDisembarkLiftFirst = false; + Vector LiftStart = ZERO_VECTOR; + Vector LiftEnd = ZERO_VECTOR; + + if (LiftReference) + { + LiftEnd = NAV_GetNearestLiftDisembarkPoint(LiftReference); + + if (!vIsZero(LiftEnd)) + { + FromLocation = LiftEnd; + + AvHAIOffMeshConnection* LiftOffMesh = UTIL_GetOffMeshConnectionForLift(LiftReference); + + if (LiftOffMesh) + { + LiftStart = (vEquals(LiftEnd, LiftOffMesh->ToLocation, 5.0f)) ? LiftOffMesh->FromLocation : LiftOffMesh->ToLocation; + bMustDisembarkLiftFirst = true; + FromFloorLocation = LiftEnd; + } + } + } + Vector ToFloorLocation = AdjustPointForPathfinding(ToLocation); float pStartPos[3] = { FromFloorLocation.x, FromFloorLocation.z, -FromFloorLocation.y }; @@ -2093,6 +2163,19 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, Vector NodeFromLocation = FromFloorLocation; + if (bMustDisembarkLiftFirst) + { + bot_path_node StartPathNode; + StartPathNode.FromLocation = LiftStart; + StartPathNode.Location = LiftEnd; + StartPathNode.flag = SAMPLE_POLYFLAGS_LIFT; + StartPathNode.area = SAMPLE_POLYAREA_LIFT; + + path.push_back(StartPathNode); + + NodeFromLocation = LiftEnd; + } + for (int nVert = 0; nVert < nVertCount; nVert++) { bot_path_node NextPathNode; @@ -3319,12 +3402,14 @@ void NewMove(AvHAIPlayer* pBot) // Used to anticipate if we're about to enter a crouch area so we can start crouching early unsigned char NextArea = SAMPLE_POLYAREA_GROUND; + SamplePolyFlags NextNavFlags = SAMPLE_POLYFLAGS_DISABLED; if (pBot->BotNavInfo.CurrentPathPoint < pBot->BotNavInfo.CurrentPath.size() - 1) { bot_path_node NextPathNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint + 1]; NextArea = NextPathNode.area; + NextNavFlags = (SamplePolyFlags)NextPathNode.flag; bool bIsNearNextPoint = (vDist2DSq(pBot->Edict->v.origin, NextPathNode.FromLocation) <= sqrf(50.0f)); @@ -3399,7 +3484,7 @@ void NewMove(AvHAIPlayer* pBot) // While moving, check to make sure we're not obstructed by a func_breakable, e.g. vent or window. CheckAndHandleBreakableObstruction(pBot, MoveFrom, MoveTo, CurrentNavFlags); - if (CurrentNavFlags != SAMPLE_POLYFLAGS_LIFT) + if (CurrentNavFlags != SAMPLE_POLYFLAGS_LIFT && NextNavFlags != SAMPLE_POLYFLAGS_LIFT) { CheckAndHandleDoorObstruction(pBot); } @@ -4083,6 +4168,61 @@ DoorTrigger* UTIL_GetDoorTriggerByEntity(edict_t* TriggerEntity) return nullptr; } +bool HasDoorBeenTriggered(nav_door* DoorRef) +{ + if (!DoorRef) { return false; } + + for (auto it = DoorRef->TriggerEnts.begin(); it != DoorRef->TriggerEnts.end(); it++) + { + if (it->bIsActivated && gpGlobals->time < it->NextActivationTime) + { + return true; + } + } + + return false; +} + +void NAV_ForceActivateTrigger(AvHAIPlayer* pBot, DoorTrigger* TriggerRef) +{ + if (!TriggerRef || !TriggerRef->Entity) { return; } + + + + switch (TriggerRef->TriggerType) + { + case DOOR_TRIGGER: + TriggerRef->Entity->Touch(pBot->Player); + break; + case DOOR_USE: + case DOOR_BUTTON: + TriggerRef->Entity->Use(pBot->Player, pBot->Player, USE_TOGGLE, 0.0f); + break; + default: + break; + } +} + +DoorTrigger* NAV_GetTriggerReachableFromLift(float LiftHeight, nav_door* Lift) +{ + DoorTrigger* Result = nullptr; + + for (auto it = Lift->TriggerEnts.begin(); it != Lift->TriggerEnts.end(); it++) + { + if (!it->bIsActivated) { continue; } + + Vector ClosestPointOnButton = UTIL_GetClosestPointOnEntityToLocation(UTIL_GetCentreOfEntity(Lift->DoorEdict), (*it).Edict); + Vector ClosestPointOnLift = UTIL_GetClosestPointOnEntityToLocation(ClosestPointOnButton, Lift->DoorEdict); + + if (vDist2DSq(ClosestPointOnButton, ClosestPointOnLift) < sqrf(max_player_use_reach) && fabs(LiftHeight - ClosestPointOnButton.z) < 64.0f) + { + return &(*it); + } + } + + return nullptr; +} + void LiftMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) { nav_door* NearestLift = UTIL_GetClosestLiftToPoints(StartPoint, EndPoint); @@ -4131,230 +4271,229 @@ void LiftMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) bool bIsOnLift = (pBot->Edict->v.groundentity == NearestLift->DoorEdict); bool bWaitingToEmbark = (!bIsOnLift && vDist3DSq(pBot->Edict->v.origin, StartPoint) < vDist3DSq(pBot->Edict->v.origin, EndPoint)) || (bIsOnLift && !bIsLiftMoving && bIsLiftAtOrNearStart); - - // Do nothing if we're on a moving lift - if (bIsLiftMoving && bIsOnLift) + if (bIsOnLift) { - Vector LiftEdge = UTIL_GetClosestPointOnEntityToLocation(StartPoint, NearestLift->DoorEdict); - - bool bFullyOnLift = vDist2DSq(pBot->Edict->v.origin, LiftEdge) > sqrf(GetPlayerRadius(pBot->Player) * 2.0f); - - if (!bFullyOnLift) + // We're approaching our destination, get off + if (bIsLiftAtOrNearEnd) { - pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - pBot->Edict->v.origin); + MoveToWithoutNav(pBot, EndPoint); + return; } - if (!UTIL_QuickHullTrace(pBot->Edict, pBot->Edict->v.origin, pBot->CollisionHullTopLocation + Vector(0.0f, 0.0f, 64.0f))) + // Either the lift is moving, or is about to start moving + if (bIsLiftMoving || (bIsLiftAtOrNearStart && HasDoorBeenTriggered(NearestLift))) { - pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - pBot->Edict->v.origin); + Vector LiftEdge = UTIL_GetClosestPointOnEntityToLocation(StartPoint, NearestLift->DoorEdict); + + bool bFullyOnLift = vDist2DSq(pBot->Edict->v.origin, LiftEdge) > sqrf(GetPlayerRadius(pBot->Player) * 2.0f); + + if (!bFullyOnLift) + { + pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - pBot->Edict->v.origin); + } + + if (!UTIL_QuickHullTrace(pBot->Edict, pBot->Edict->v.origin, pBot->CollisionHullTopLocation + Vector(0.0f, 0.0f, 64.0f))) + { + pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - pBot->Edict->v.origin); + } + + return; } + // We're on the lift, but it's not about to start moving. + + + // We're trapped half-way. Some dick must have hit the button and stopped the lift part-way + // Activate force powers! + if (!bIsLiftAtOrNearStart) + { + for (auto it = NearestLift->TriggerEnts.begin(); it != NearestLift->TriggerEnts.end(); it++) + { + if (it->bIsActivated) + { + NAV_ForceActivateTrigger(pBot, &(*it)); + return; + } + } + } + + // We're not trapped, see if there is a button we can reach from where we are + + DoorTrigger* NearestTrigger = NAV_GetTriggerReachableFromLift(pBot->Edict->v.origin.z, NearestLift); + + if (NearestTrigger) + { + if (IsPlayerInUseRange(pBot->Edict, NearestTrigger->Edict)) + { + BotUseObject(pBot, NearestTrigger->Edict, false); + return; + } + else + { + pBot->desiredMovementDir = UTIL_GetVectorNormal2D(UTIL_GetCentreOfEntity(NearestTrigger->Edict) - pBot->Edict->v.origin); + } + + return; + } + + // Otherwise, we're able to get back to the start, so do that + MoveToWithoutNav(pBot, StartPoint); return; + } // if we've reached our stop, or we can directly get to the end point. Move straight there - - Vector BotNavPosition = UTIL_ProjectPointToNavmesh(pBot->CollisionHullBottomLocation); - - BotNavPosition = (vIsZero(BotNavPosition)) ? pBot->CollisionHullBottomLocation : BotNavPosition; - - if ((bIsOnLift && !bIsLiftMoving && bIsLiftAtOrNearEnd) || UTIL_PointIsDirectlyReachable(BotNavPosition, EndPoint)) + if (UTIL_PointIsDirectlyReachable(pBot->CollisionHullBottomLocation, EndPoint)) { MoveToWithoutNav(pBot, EndPoint); return; } - // We must be either waiting to embark, or we've stopped elsewhere on our journey and need to get the lift moving again - - // Lift is leaving without us! Get on it quick - if (bIsLiftMoving && !bIsLiftMovingToStart && bIsLiftAtOrNearStart && !bIsOnLift) + // Lift is at the embark point + if (bIsLiftAtOrNearStart) { - pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - pBot->CollisionHullBottomLocation); - BotJump(pBot); - return; - } - - if (bIsLiftMoving) - { - if (vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(50.0f)) + // Lift has been triggered or started moving, get on board before it leaves + if (HasDoorBeenTriggered(NearestLift) || bIsLiftMovingToEnd) { - NAV_SetMoveMovementTask(pBot, StartPoint, nullptr); - } - return; - } - - if (bIsLiftAtOrNearStart && vEquals(DesiredStartStop, DesiredEndStop)) - { - if (!bIsOnLift) - { - if (vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(50.0f)) - { - NAV_SetMoveMovementTask(pBot, StartPoint, nullptr); - return; - } - - pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - pBot->CollisionHullBottomLocation); - } - return; - } - - // Lift is stopped somewhere else, summon it - if (!bIsLiftMoving) - { - DoorTrigger* NearestLiftTrigger = nullptr; - - if (bIsLiftAtOrNearStart) - { - float NearestDist = 0.0f; - - for (auto it = NearestLift->TriggerEnts.begin(); it != NearestLift->TriggerEnts.end(); it++) - { - if (it->bIsActivated) - { - Vector CheckLocation = UTIL_GetCentreOfEntity(NearestLift->DoorEdict); - CheckLocation.z = pBot->Edict->v.origin.z; - - Vector ButtonLocation = UTIL_GetClosestPointOnEntityToLocation(CheckLocation, it->Edict); - Vector NearestPointOnLift = UTIL_GetClosestPointOnEntityToLocation(ButtonLocation, NearestLift->DoorEdict); - - NearestPointOnLift.z = pBot->Edict->v.origin.z; - - float thisDist = vDist3DSq(ButtonLocation, NearestPointOnLift); - - if (thisDist < sqrf(64.0f)) - { - if (!NearestLiftTrigger || thisDist < NearestDist) - { - NearestLiftTrigger = &(*it); - NearestDist = thisDist; - } - - } - } - } - } - - if (!NearestLiftTrigger) - { - NearestLiftTrigger = UTIL_GetNearestDoorTrigger(pBot->Edict->v.origin, NearestLift, nullptr, false); - } - - if (NearestLiftTrigger) - { - // If the trigger is on cooldown, or the door/train is designed to automatically return without being summoned, then just wait for it to come back - if (gpGlobals->time < NearestLiftTrigger->NextActivationTime || (NearestLift->DoorType == DOORTYPE_TRAIN && !(NearestLift->DoorEdict->v.spawnflags & SF_TRAIN_WAIT_RETRIGGER)) || (NearestLift->DoorType == DOORTYPE_DOOR && NearestLift->DoorEntity && NearestLift->DoorEntity->GetToggleState() == TS_AT_TOP && NearestLift->DoorEntity->m_flWait > 0.0f && !FBitSet(NearestLift->DoorEdict->v.spawnflags, SF_DOOR_NO_AUTO_RETURN))) - { - if (!bIsOnLift && !bIsLiftAtOrNearStart) - { - // Make sure we won't be squashed by the lift coming down on us - if (vBBOverlaps2D(pBot->Edict->v.absmin, pBot->Edict->v.absmax, NearestLift->DoorEdict->v.absmin, NearestLift->DoorEdict->v.absmax)) - { - pBot->desiredMovementDir = UTIL_GetVectorNormal2D(StartPoint - DesiredStartStop); - } - else - { - if (vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(32.0f)) - { - NAV_SetMoveMovementTask(pBot, StartPoint, nullptr); - } - } - - } - else - { - bool bFullyOnLift = false; - - if (bIsOnLift) - { - Vector LiftEdge = UTIL_GetClosestPointOnEntityToLocation(StartPoint, NearestLift->DoorEdict); - - bFullyOnLift = vDist2DSq(pBot->Edict->v.origin, LiftEdge) > (sqrf(GetPlayerRadius(pBot->Player) * 1.1f) ); - } - - if (bIsLiftAtOrNearStart && (!bIsOnLift || !bFullyOnLift) ) - { - pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - StartPoint); - } - } - return; - } - - if (bIsLiftAtOrNearStart) - { - Vector ButtonFloorLocation = UTIL_GetClosestPointOnEntityToLocation(pBot->Edict->v.origin, NearestLiftTrigger->Edict); - - Vector NearestPointOnLiftToButton = UTIL_GetClosestPointOnEntityToLocation(ButtonFloorLocation, NearestLift->DoorEdict); - - bool ButtonReachableFromLift = (NearestLiftTrigger->TriggerType == DOOR_TRIGGER) ? vBBOverlaps2D(NearestLiftTrigger->Edict->v.absmin, NearestLiftTrigger->Edict->v.absmax, NearestLift->DoorEdict->v.absmin, NearestLift->DoorEdict->v.absmax) : (!vIsZero(ButtonFloorLocation) && (vDist2DSq(ButtonFloorLocation, NearestPointOnLiftToButton) <= sqrf(64.0f))); - - if (ButtonReachableFromLift) - { - if (NearestLiftTrigger->TriggerType == DOOR_BUTTON) - { - if (IsPlayerInUseRange(pBot->Edict, NearestLiftTrigger->Edict)) - { - BotUseObject(pBot, NearestLiftTrigger->Edict, false); - return; - } - } - else - { - pBot->desiredMovementDir = UTIL_GetVectorNormal2D(NearestLiftTrigger->Edict->v.origin - pBot->Edict->v.origin); - } - - if (!bIsOnLift) - { - pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - pBot->Edict->v.origin); - return; - } - else - { - pBot->desiredMovementDir = UTIL_GetVectorNormal2D(ButtonFloorLocation - pBot->Edict->v.origin); - return; - } - } - } - - if (NearestLiftTrigger->TriggerType == DOOR_BUTTON) - { - Vector UseLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, NearestLiftTrigger->Edict); - - NAV_SetUseMovementTask(pBot, NearestLiftTrigger->Edict, NearestLiftTrigger); - } - else if (NearestLiftTrigger->TriggerType == DOOR_TRIGGER) - { - NAV_SetTouchMovementTask(pBot, NearestLiftTrigger->Edict, NearestLiftTrigger); - } - else if (NearestLiftTrigger->TriggerType == DOOR_WELD) - { - NAV_SetWeldMovementTask(pBot, NearestLiftTrigger->Edict, NearestLiftTrigger); - } - else if (NearestLiftTrigger->TriggerType == DOOR_BREAK) - { - NAV_SetBreakMovementTask(pBot, NearestLiftTrigger->Edict, NearestLiftTrigger); - } - - return; - } - else - { - if (!bIsOnLift && vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(50.0f) && !bIsLiftAtOrNearStart) + if (vDistanceFromLine2D(StartPoint, LiftPosition, pBot->Edict->v.origin) > sqrf(50.0f)) { NAV_SetMoveMovementTask(pBot, StartPoint, nullptr); } else { - if (!bIsOnLift && bIsLiftAtOrNearStart) + MoveToWithoutNav(pBot, LiftPosition); + } + + + return; + } + + if (bIsLiftMovingToStart) + { + if (vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(100.0f)) + { + NAV_SetMoveMovementTask(pBot, StartPoint, nullptr); + } + else + { + if (vBBOverlaps2D(pBot->Edict->v.absmin, pBot->Edict->v.absmax, NearestLift->DoorEdict->v.absmin, NearestLift->DoorEdict->v.absmax)) { - pBot->desiredMovementDir = UTIL_GetVectorNormal2D(LiftPosition - StartPoint); + Vector NearestPointOnLift = UTIL_GetClosestPointOnEntityToLocation(pBot->Edict->v.origin, NearestLift->DoorEdict); + pBot->desiredMovementDir = UTIL_GetVectorNormal2D(pBot->Edict->v.origin - LiftPosition); + } + } + return; + } + + DoorTrigger* TriggerToActivate = NAV_GetTriggerReachableFromLift(pBot->Edict->v.origin.z, NearestLift); + + if (TriggerToActivate) + { + if (vDistanceFromLine2D(StartPoint, LiftPosition, pBot->Edict->v.origin) > sqrf(50.0f)) + { + NAV_SetMoveMovementTask(pBot, StartPoint, nullptr); + } + else + { + MoveToWithoutNav(pBot, LiftPosition); + } + + return; + } + + TriggerToActivate = UTIL_GetNearestDoorTrigger(pBot->Edict->v.origin, NearestLift, nullptr, false); + + if (!TriggerToActivate) + { + for (auto it = NearestLift->TriggerEnts.begin(); it != NearestLift->TriggerEnts.end(); it++) + { + if (it->bIsActivated) + { + NAV_ForceActivateTrigger(pBot, &(*it)); + return; } } } + else + { + if (TriggerToActivate->TriggerType == DOOR_BUTTON) + { + Vector UseLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, TriggerToActivate->Edict); - + NAV_SetUseMovementTask(pBot, TriggerToActivate->Edict, TriggerToActivate); + } + else if (TriggerToActivate->TriggerType == DOOR_TRIGGER) + { + NAV_SetTouchMovementTask(pBot, TriggerToActivate->Edict, TriggerToActivate); + } + else if (TriggerToActivate->TriggerType == DOOR_WELD) + { + NAV_SetWeldMovementTask(pBot, TriggerToActivate->Edict, TriggerToActivate); + } + else if (TriggerToActivate->TriggerType == DOOR_BREAK) + { + NAV_SetBreakMovementTask(pBot, TriggerToActivate->Edict, TriggerToActivate); + } + } + return; } + // Lift is at the end point, summon it + + // Lift is on its way + if (HasDoorBeenTriggered(NearestLift) || bIsLiftMoving) + { + if (vDist2DSq(pBot->Edict->v.origin, StartPoint) > sqrf(100.0f)) + { + NAV_SetMoveMovementTask(pBot, StartPoint, nullptr); + } + else + { + if (vBBOverlaps2D(pBot->Edict->v.absmin, pBot->Edict->v.absmax, NearestLift->DoorEdict->v.absmin, NearestLift->DoorEdict->v.absmax)) + { + Vector NearestPointOnLift = UTIL_GetClosestPointOnEntityToLocation(pBot->Edict->v.origin, NearestLift->DoorEdict); + pBot->desiredMovementDir = UTIL_GetVectorNormal2D(pBot->Edict->v.origin - LiftPosition); + } + } + return; + } + + DoorTrigger* TriggerToActivate = UTIL_GetNearestDoorTrigger(pBot->Edict->v.origin, NearestLift, nullptr, false); + + if (!TriggerToActivate) + { + for (auto it = NearestLift->TriggerEnts.begin(); it != NearestLift->TriggerEnts.end(); it++) + { + if (it->bIsActivated) + { + NAV_ForceActivateTrigger(pBot, &(*it)); + return; + } + } + } + else + { + if (TriggerToActivate->TriggerType == DOOR_BUTTON) + { + Vector UseLocation = UTIL_GetButtonFloorLocation(pBot->Edict->v.origin, TriggerToActivate->Edict); + + NAV_SetUseMovementTask(pBot, TriggerToActivate->Edict, TriggerToActivate); + } + else if (TriggerToActivate->TriggerType == DOOR_TRIGGER) + { + NAV_SetTouchMovementTask(pBot, TriggerToActivate->Edict, TriggerToActivate); + } + else if (TriggerToActivate->TriggerType == DOOR_WELD) + { + NAV_SetWeldMovementTask(pBot, TriggerToActivate->Edict, TriggerToActivate); + } + else if (TriggerToActivate->TriggerType == DOOR_BREAK) + { + NAV_SetBreakMovementTask(pBot, TriggerToActivate->Edict, TriggerToActivate); + } + } + } void PhaseGateMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) @@ -4471,12 +4610,12 @@ bool IsBotOffWalkNode(const AvHAIPlayer* pBot, Vector MoveStart, Vector MoveEnd, if (vDist2DSq(pBot->Edict->v.origin, NearestPointOnLine) > sqrf(GetPlayerRadius(pBot->Edict) * 3.0f)) { return true; } - if (!FNullEnt(pBot->Edict->v.groundentity)) - { - nav_door* Door = UTIL_GetNavDoorByEdict(pBot->Edict->v.groundentity); + //if (!FNullEnt(pBot->Edict->v.groundentity)) + //{ + // nav_door* Door = UTIL_GetNavDoorByEdict(pBot->Edict->v.groundentity); - if (Door) { return false; } - } + // if (Door) { return false; } + //} if (vEquals2D(NearestPointOnLine, MoveStart) && !UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveStart)) { return true; } if (vEquals2D(NearestPointOnLine, MoveEnd) && !UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, MoveEnd)) { return true; } @@ -6067,7 +6206,7 @@ void OnosUpdateBotMoveProfile(AvHAIPlayer* pBot, BotMoveStyle MoveStyle) bool NAV_MergeAndUpdatePath(AvHAIPlayer* pBot, std::vector& NewPath) { - if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size()) + if (pBot->BotNavInfo.NavProfile.bFlyingProfile || pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size()) { pBot->BotNavInfo.CurrentPath.clear(); pBot->BotNavInfo.CurrentPath.insert(pBot->BotNavInfo.CurrentPath.end(), NewPath.begin(), NewPath.end()); @@ -6122,15 +6261,106 @@ bool NAV_MergeAndUpdatePath(AvHAIPlayer* pBot, std::vector& NewPa return true; } +bool NAV_GenerateNewBasePath(AvHAIPlayer* pBot, const Vector NewDestination, const BotMoveStyle MoveStyle, const float MaxAcceptableDist) +{ + nav_status* BotNavInfo = &pBot->BotNavInfo; + + dtStatus PathFindingStatus = DT_FAILURE; + + vector PendingPath; + bool bIsFlyingProfile = BotNavInfo->NavProfile.bFlyingProfile; + + + if (bIsFlyingProfile) + { + PathFindingStatus = FindFlightPathToPoint(BotNavInfo->NavProfile, pBot->Edict->v.origin, NewDestination, PendingPath, MaxAcceptableDist); + } + else + { + Vector NavAdjustedDestination = AdjustPointForPathfinding(NewDestination); + if (vIsZero(NavAdjustedDestination)) { return false; } + + PathFindingStatus = FindPathClosestToPoint(pBot, BotNavInfo->MoveStyle, NavAdjustedDestination, PendingPath, MaxAcceptableDist); + } + + BotNavInfo->NextForceRecalc = 0.0f; + BotNavInfo->bNavProfileChanged = false; + + if (dtStatusSucceed(PathFindingStatus)) + { + if (!NAV_MergeAndUpdatePath(pBot, PendingPath)) + { + if (!AbortCurrentMove(pBot, NewDestination)) + { + return true; + } + else + { + ClearBotPath(pBot); + NAV_ClearMovementTask(pBot); + pBot->BotNavInfo.CurrentPath.insert(pBot->BotNavInfo.CurrentPath.begin(), PendingPath.begin(), PendingPath.end()); + BotNavInfo->CurrentPathPoint = 0; + } + } + + BotNavInfo->ActualMoveDestination = BotNavInfo->CurrentPath.back().Location; + BotNavInfo->TargetDestination = NewDestination; + + pBot->BotNavInfo.StuckInfo.bPathFollowFailed = false; + ClearBotStuckMovement(pBot); + pBot->BotNavInfo.TotalStuckTime = 0.0f; + BotNavInfo->PathDestination = NewDestination; + + return true; + } + + return false; +} + +bool NAV_GenerateNewMoveTaskPath(AvHAIPlayer* pBot, const Vector NewDestination, const BotMoveStyle MoveStyle) +{ + nav_status* BotNavInfo = &pBot->BotNavInfo; + + dtStatus PathFindingStatus = DT_FAILURE; + + vector PendingPath; + bool bIsFlyingProfile = BotNavInfo->NavProfile.bFlyingProfile; + + if (bIsFlyingProfile) + { + PathFindingStatus = FindFlightPathToPoint(BotNavInfo->NavProfile, pBot->Edict->v.origin, NewDestination, PendingPath, max_player_use_reach); + } + else + { + Vector NavAdjustedDestination = AdjustPointForPathfinding(NewDestination); + if (vIsZero(NavAdjustedDestination)) { return false; } + + PathFindingStatus = FindPathClosestToPoint(pBot, BotNavInfo->MoveStyle, NavAdjustedDestination, PendingPath, max_player_use_reach); + } + + if (dtStatusSucceed(PathFindingStatus)) + { + pBot->BotNavInfo.CurrentPath.clear(); + pBot->BotNavInfo.CurrentPathPoint = 0; + pBot->BotNavInfo.SpecialMovementFlags = 0; + + pBot->BotNavInfo.CurrentPath.insert(pBot->BotNavInfo.CurrentPath.begin(), PendingPath.begin(), PendingPath.end()); + BotNavInfo->CurrentPathPoint = 0; + + pBot->BotNavInfo.StuckInfo.bPathFollowFailed = false; + ClearBotStuckMovement(pBot); + pBot->BotNavInfo.TotalStuckTime = 0.0f; + BotNavInfo->PathDestination = NewDestination; + + return true; + } + + return false; +} + bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle MoveStyle, const float MaxAcceptableDist) { -#ifdef DEBUG - if (pBot == AIMGR_GetDebugAIPlayer()) - { - bool bBreak = true; // Add a break point here if you want to debug a specific bot - } -#endif - + // Trying to move nowhere, or our current location. Do nothing if (vIsZero(Destination) || (vDist2D(pBot->Edict->v.origin, Destination) <= 6.0f && (fabs(pBot->CollisionHullBottomLocation.z - Destination.z) < 50.0f))) { pBot->BotNavInfo.StuckInfo.bPathFollowFailed = false; @@ -6150,110 +6380,110 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move bool bForceRecalculation = (pBot->BotNavInfo.NextForceRecalc > 0.0f && gpGlobals->time >= pBot->BotNavInfo.NextForceRecalc); bool bIsPerformingMoveTask = (BotNavInfo->MovementTask.TaskType != MOVE_TASK_NONE && vEquals(Destination, BotNavInfo->MovementTask.TaskLocation, GetPlayerRadius(pBot->Player))); bool bEndGoalChanged = (!vEquals(Destination, BotNavInfo->TargetDestination, GetPlayerRadius(pBot->Player)) && !bIsPerformingMoveTask); - bool bMoveTaskGenerated = (BotNavInfo->MovementTask.TaskType == MOVE_TASK_NONE || (vEquals(BotNavInfo->PathDestination, BotNavInfo->MovementTask.TaskLocation, GetPlayerRadius(pBot->Player)))); + + bool bShouldGenerateMainPath = (bEndGoalChanged || bNavProfileChanged || bForceRecalculation); + bool bShouldGenerateMoveTaskPath = (bIsPerformingMoveTask && !vEquals(BotNavInfo->PathDestination, BotNavInfo->MovementTask.TaskLocation, GetPlayerRadius(pBot->Player))); - - // Only recalculate the path if there isn't a path, or something has changed and enough time has elapsed since the last path calculation - bool bShouldCalculatePath = (bNavProfileChanged || bForceRecalculation || BotNavInfo->CurrentPath.size() == 0 || bEndGoalChanged || !bMoveTaskGenerated); - - if (bShouldCalculatePath) + if (bShouldGenerateMainPath || bShouldGenerateMoveTaskPath) { if (!bIsFlyingProfile && !pBot->BotNavInfo.IsOnGround && !IsPlayerClimbingWall(pBot->Edict)) - { + { if (pBot->BotNavInfo.CurrentPath.size() > 0) { BotFollowPath(pBot); } - return true; + return true; } - dtStatus PathFindingStatus = DT_FAILURE; - - vector PendingPath; - - if (bIsFlyingProfile) + if (bShouldGenerateMainPath) { - PathFindingStatus = FindFlightPathToPoint(pBot->BotNavInfo.NavProfile, pBot->Edict->v.origin, Destination, PendingPath, MaxAcceptableDist); - } - else - { - Vector NavAdjustedDestination = AdjustPointForPathfinding(Destination); - if (vIsZero(NavAdjustedDestination)) { return false; } + bool bSucceeded = NAV_GenerateNewBasePath(pBot, Destination, MoveStyle, MaxAcceptableDist); - PathFindingStatus = FindPathClosestToPoint(pBot, pBot->BotNavInfo.MoveStyle, pBot->CurrentFloorPosition, NavAdjustedDestination, PendingPath, MaxAcceptableDist); - } - - pBot->BotNavInfo.NextForceRecalc = 0.0f; - pBot->BotNavInfo.bNavProfileChanged = false; - - if (dtStatusSucceed(PathFindingStatus)) - { - if (!NAV_MergeAndUpdatePath(pBot, PendingPath)) + if (!bSucceeded) { - if (!AbortCurrentMove(pBot, Destination)) + if (pBot->BotNavInfo.CurrentPath.size() == 0) { - return true; - } - else - { - ClearBotPath(pBot); - NAV_ClearMovementTask(pBot); - pBot->BotNavInfo.CurrentPath.insert(pBot->BotNavInfo.CurrentPath.begin(), PendingPath.begin(), PendingPath.end()); - BotNavInfo->CurrentPathPoint = 0; - } - } + pBot->BotNavInfo.StuckInfo.bPathFollowFailed = true; - pBot->BotNavInfo.StuckInfo.bPathFollowFailed = false; - ClearBotStuckMovement(pBot); - pBot->BotNavInfo.TotalStuckTime = 0.0f; - BotNavInfo->PathDestination = Destination; - - if (!bIsPerformingMoveTask) - { - BotNavInfo->ActualMoveDestination = BotNavInfo->CurrentPath.back().Location; - BotNavInfo->TargetDestination = Destination; - } - - - } - else - { - if (pBot->BotNavInfo.CurrentPath.size() == 0) - { - pBot->BotNavInfo.StuckInfo.bPathFollowFailed = true; - - if (!UTIL_PointIsOnNavmesh(pBot->CollisionHullBottomLocation, pBot->BotNavInfo.NavProfile) && !vIsZero(BotNavInfo->LastNavMeshPosition)) - { - MoveDirectlyTo(pBot, BotNavInfo->LastNavMeshPosition); - - if (vDist2DSq(pBot->CurrentFloorPosition, BotNavInfo->LastNavMeshPosition) < sqrf(8.0f)) + if (!UTIL_PointIsOnNavmesh(pBot->CollisionHullBottomLocation, pBot->BotNavInfo.NavProfile) && !vIsZero(BotNavInfo->LastNavMeshPosition)) { - BotNavInfo->LastNavMeshPosition = g_vecZero; - } + MoveDirectlyTo(pBot, BotNavInfo->LastNavMeshPosition); - return true; - } - else - { - if (!vIsZero(BotNavInfo->UnstuckMoveLocation) && vDist2DSq(pBot->CurrentFloorPosition, BotNavInfo->UnstuckMoveLocation) < sqrf(8.0f)) - { - BotNavInfo->UnstuckMoveLocation = ZERO_VECTOR; - } + if (vDist2DSq(pBot->CurrentFloorPosition, BotNavInfo->LastNavMeshPosition) < sqrf(8.0f)) + { + BotNavInfo->LastNavMeshPosition = g_vecZero; + } - if (vIsZero(BotNavInfo->UnstuckMoveLocation)) - { - BotNavInfo->UnstuckMoveLocation = FindClosestPointBackOnPath(pBot); - } - - if (!vIsZero(BotNavInfo->UnstuckMoveLocation)) - { - MoveDirectlyTo(pBot, BotNavInfo->UnstuckMoveLocation); return true; } + else + { + if (!vIsZero(BotNavInfo->UnstuckMoveLocation) && vDist2DSq(pBot->CurrentFloorPosition, BotNavInfo->UnstuckMoveLocation) < sqrf(8.0f)) + { + BotNavInfo->UnstuckMoveLocation = ZERO_VECTOR; + } + + if (vIsZero(BotNavInfo->UnstuckMoveLocation)) + { + BotNavInfo->UnstuckMoveLocation = FindClosestPointBackOnPath(pBot); + } + + if (!vIsZero(BotNavInfo->UnstuckMoveLocation)) + { + MoveDirectlyTo(pBot, BotNavInfo->UnstuckMoveLocation); + return true; + } + } } + else + { + BotFollowPath(pBot); + } + return false; } } + else + { + bool bSucceeded = NAV_GenerateNewMoveTaskPath(pBot, BotNavInfo->MovementTask.TaskLocation, MoveStyle); + + if (!bSucceeded) + { + if (!FNullEnt(pBot->Edict->v.groundentity)) + { + nav_door* Door = UTIL_GetNavDoorByEdict(pBot->Edict->v.groundentity); + + if (Door) + { + + } + } + + + if (!FNullEnt(BotNavInfo->MovementTask.TaskTarget)) + { + CBaseEntity* TargetEntity = CBaseEntity::Instance(BotNavInfo->MovementTask.TaskTarget); + + if (!TargetEntity) { return false; } + + switch (BotNavInfo->MovementTask.TaskType) + { + case MOVE_TASK_TOUCH: + TargetEntity->Touch(pBot->Player); + return true; + break; + case MOVE_TASK_USE: + TargetEntity->Use(pBot->Player, pBot->Player, USE_TOGGLE, 0.0f); + return true; + break; + default: + break; + + } + } + } + } + } if (!bIsPerformingMoveTask && BotNavInfo->MovementTask.TaskType != MOVE_TASK_NONE) @@ -6408,11 +6638,14 @@ void SkipAheadInFlightPath(AvHAIPlayer* pBot) if (UTIL_QuickHullTrace(pBot->Edict, pBot->Edict->v.origin, prev(BotNavInfo->CurrentPath.end())->Location, head_hull, false)) { pBot->BotNavInfo.CurrentPathPoint = (BotNavInfo->CurrentPath.size() - 1); + BotNavInfo->CurrentPath[pBot->BotNavInfo.CurrentPathPoint].FromLocation = pBot->Edict->v.origin; return; } - // If we are currently in a low area or approaching one, don't try to skip ahead in case it screws us up - if (CurrentPathPoint->area == SAMPLE_POLYAREA_CROUCH || (next(CurrentPathPoint) != BotNavInfo->CurrentPath.end() && next(CurrentPathPoint)->area == SAMPLE_POLYAREA_CROUCH)) { return; } + // If we are approaching a low area or lift, don't try to skip ahead in case it screws us up + if (CurrentPathPoint->area == SAMPLE_POLYAREA_CROUCH + || CurrentPathPoint->area == SAMPLE_POLYAREA_LIFT + || (next(CurrentPathPoint) != BotNavInfo->CurrentPath.end() && (next(CurrentPathPoint)->area == SAMPLE_POLYAREA_CROUCH || next(CurrentPathPoint)->area == SAMPLE_POLYAREA_LIFT))) { return; } for (auto it = prev(BotNavInfo->CurrentPath.end()); it != next(CurrentPathPoint); it--) { @@ -6471,13 +6704,24 @@ void BotFollowFlightPath(AvHAIPlayer* pBot, bool bAllowSkip) } } + + if (CurrentPathPoint->flag == SAMPLE_POLYFLAGS_LIFT) { LiftMove(pBot, CurrentPathPoint->FromLocation, CurrentMoveDest); return; } - if (bAllowSkip && CurrentPathPoint->area != SAMPLE_POLYAREA_CROUCH && next(CurrentPathPoint) != BotNavInfo->CurrentPath.end() && next(CurrentPathPoint)->area != SAMPLE_POLYAREA_CROUCH) + SamplePolyAreas NextArea = SAMPLE_POLYAREA_GROUND; + SamplePolyFlags NextFlags = SAMPLE_POLYFLAGS_DISABLED; + + if (next(CurrentPathPoint) != BotNavInfo->CurrentPath.end()) + { + NextArea = (SamplePolyAreas)next(CurrentPathPoint)->area; + NextFlags = (SamplePolyFlags)next(CurrentPathPoint)->flag; + } + + if (bAllowSkip && CurrentPathPoint->area != SAMPLE_POLYAREA_CROUCH && CurrentPathPoint->flag != SAMPLE_POLYFLAGS_LIFT && NextArea != SAMPLE_POLYAREA_CROUCH && NextFlags != SAMPLE_POLYFLAGS_LIFT) { SkipAheadInFlightPath(pBot); CurrentPathPoint = (BotNavInfo->CurrentPath.begin() + BotNavInfo->CurrentPathPoint); @@ -6691,7 +6935,7 @@ void BotFollowPath(AvHAIPlayer* pBot) pBot->desiredMovementDir = UTIL_GetVectorNormal2D(-pBot->Edict->v.groundentity->v.velocity); return; } - MoveToWithoutNav(pBot, CurrentNode.Location); + pBot->desiredMovementDir = -UTIL_GetForwardVector2D(pBot->Edict->v.angles); return; } @@ -6701,20 +6945,8 @@ void BotFollowPath(AvHAIPlayer* pBot) { if ((*it)->pev->groundentity == pBot->Edict) { - if (vDist2DSq(pBot->Edict->v.origin, CurrentNode.FromLocation) > sqrf(GetPlayerRadius(pBot->Edict))) - { - MoveToWithoutNav(pBot, CurrentNode.FromLocation); - return; - } - else - { - if (pBot->BotNavInfo.CurrentPathPoint > 0) - { - bot_path_node PrevNode = pBot->BotNavInfo.CurrentPath[pBot->BotNavInfo.CurrentPathPoint - 1]; - MoveToWithoutNav(pBot, PrevNode.FromLocation); - return; - } - } + pBot->desiredMovementDir = UTIL_GetForwardVector2D(pBot->Edict->v.angles); + return; } } @@ -7186,7 +7418,7 @@ bool BotRecalcPath(AvHAIPlayer* pBot, const Vector Destination) return false; } - dtStatus FoundPath = FindPathClosestToPoint(pBot, pBot->BotNavInfo.MoveStyle, pBot->CurrentFloorPosition, ValidNavmeshPoint, pBot->BotNavInfo.CurrentPath, max_ai_use_reach); + dtStatus FoundPath = FindPathClosestToPoint(pBot, pBot->BotNavInfo.MoveStyle, ValidNavmeshPoint, pBot->BotNavInfo.CurrentPath, max_ai_use_reach); if (dtStatusSucceed(FoundPath) && pBot->BotNavInfo.CurrentPath.size() > 0) { @@ -8069,25 +8301,36 @@ void UTIL_UpdateDoors(bool bInitial) Vector DoorCentre = UTIL_GetCentreOfEntity(NavDoor->DoorEdict); - bool bThisConnectionAffected = false; + bool bThisConnectionAffected = (vlineIntersectsAABB(ConnStart, MidPoint, NavDoor->DoorEdict->v.absmin, NavDoor->DoorEdict->v.absmax) || vlineIntersectsAABB(MidPoint, ConnEnd, NavDoor->DoorEdict->v.absmin, NavDoor->DoorEdict->v.absmax)); - Vector NearestPointOnLine = vClosestPointOnLine(ConnStart, MidPoint, DoorCentre); - if (vPointOverlaps3D(NearestPointOnLine, DoorCentre - HalfExtents, DoorCentre + HalfExtents)) + if (bThisConnectionAffected) { - UTIL_ModifyOffMeshConnectionFlag(ThisConnection, SAMPLE_POLYFLAGS_WELD); - bThisConnectionAffected = true; - } - else - { - NearestPointOnLine = vClosestPointOnLine(MidPoint, ConnEnd, DoorCentre); - if (vPointOverlaps3D(NearestPointOnLine, DoorCentre - HalfExtents, DoorCentre + HalfExtents)) + if (ThisConnection->bBiDirectional) { UTIL_ModifyOffMeshConnectionFlag(ThisConnection, SAMPLE_POLYFLAGS_WELD); - bThisConnectionAffected = true; + } + else + { + DoorTrigger* NearestTrigger = UTIL_GetNearestDoorTrigger(ConnStart, NavDoor, nullptr, true); + + if (!NearestTrigger) + { + UTIL_ModifyOffMeshConnectionFlag(ThisConnection, SAMPLE_POLYFLAGS_DISABLED); + } + else + { + if (NearestTrigger->TriggerType == DOOR_WELD) + { + UTIL_ModifyOffMeshConnectionFlag(ThisConnection, SAMPLE_POLYFLAGS_WELD); + } + else + { + UTIL_ModifyOffMeshConnectionFlag(ThisConnection, ThisConnection->DefaultConnectionFlags); + } + } } } - - if (!bThisConnectionAffected) + else { if (ThisConnection->ConnectionFlags != ThisConnection->DefaultConnectionFlags) { @@ -8252,6 +8495,8 @@ void UTIL_UpdateDoorTriggers(nav_door* Door) DoorActivationType NewActivationType = DOOR_NONE; + bool bButtonHasBeenPressed = false; + for (auto it = Door->TriggerEnts.begin(); it != Door->TriggerEnts.end();) { if (FNullEnt(it->Edict) || it->Edict->free) @@ -8335,6 +8580,7 @@ void UTIL_UpdateDoorTriggers(nav_door* Door) if (it->LastToggleState == TS_AT_BOTTOM || (bButtonIsToggle && it->LastToggleState == TS_AT_TOP)) { it->NextActivationTime = gpGlobals->time + fmaxf(it->ActivationDelay, 1.0f); + bButtonHasBeenPressed = true; } it->LastToggleState = NewState; @@ -8343,6 +8589,14 @@ void UTIL_UpdateDoorTriggers(nav_door* Door) it++; } + if (bButtonHasBeenPressed) + { + for (auto it = Door->TriggerEnts.begin(); it != Door->TriggerEnts.end(); it++) + { + it->NextActivationTime = gpGlobals->time + fmaxf(it->ActivationDelay, 1.0f); + } + } + Door->ActivationType = NewActivationType; } @@ -8536,6 +8790,31 @@ nav_door* UTIL_GetNavDoorByEdict(const edict_t* DoorEdict) return nullptr; } +AvHAIOffMeshConnection* UTIL_GetOffMeshConnectionForLift(nav_door* LiftRef) +{ + if (!LiftRef) { return nullptr; } + + AvHAIOffMeshConnection* NearestConnection = nullptr; + float MinDist = 0.0f; + + for (auto it = BaseMapConnections.begin(); it != BaseMapConnections.end(); it++) + { + if (!(it->ConnectionFlags & SAMPLE_POLYFLAGS_LIFT)) { continue; } + + Vector LiftLocation = UTIL_GetCentreOfEntity(LiftRef->DoorEdict); + + float ThisDist = fminf(vDist3DSq(it->FromLocation, LiftLocation), vDist3DSq(it->ToLocation, LiftLocation)); + + if (!NearestConnection || ThisDist < MinDist) + { + NearestConnection = &(*it); + MinDist = ThisDist; + } + } + + return NearestConnection; +} + // TODO: Find the topmost point when open, and topmost point when closed, and see how closely they align to the top and bottom point parameters nav_door* UTIL_GetClosestLiftToPoints(const Vector StartPoint, const Vector EndPoint) { @@ -8944,6 +9223,7 @@ bool NAV_IsMovementTaskStillValid(AvHAIPlayer* pBot) if (MoveTask->TaskType == MOVE_TASK_USE) { + if (MoveTask->TriggerToActivate) return MoveTask->TriggerToActivate && MoveTask->TriggerToActivate->bIsActivated && MoveTask->TriggerToActivate->NextActivationTime < gpGlobals->time; } diff --git a/main/source/mod/AvHAINavigation.h b/main/source/mod/AvHAINavigation.h index 56be92d2..0966f9c6 100644 --- a/main/source/mod/AvHAINavigation.h +++ b/main/source/mod/AvHAINavigation.h @@ -257,6 +257,9 @@ DoorTrigger* UTIL_GetDoorTriggerByEntity(edict_t* TriggerEntity); bool UTIL_TriggerHasBeenRecentlyActivated(edict_t* TriggerEntity); +// Directly calls the Use function on this trigger, regardless of where the bot is +void NAV_ForceActivateTrigger(AvHAIPlayer* pBot, DoorTrigger* TriggerRef); + // Will check for any func_breakable which might be in the way (e.g. window, vent) and make the bot aim and attack it to break it. Marines will switch to knife to break it. void CheckAndHandleBreakableObstruction(AvHAIPlayer* pBot, const Vector MoveFrom, const Vector MoveTo, unsigned int MovementFlags); @@ -349,13 +352,13 @@ void HandlePlayerAvoidance(AvHAIPlayer* pBot, const Vector MoveDestination); Vector AdjustPointForPathfinding(const Vector Point); Vector AdjustPointForPathfinding(const Vector Point, const nav_profile& NavProfile); -// Special path finding that takes the presence of phase gates into account +// Special path finding for lerks dtStatus FindFlightPathToPoint(const nav_profile& NavProfile, Vector FromLocation, Vector ToLocation, vector& path, float MaxAcceptableDistance); Vector UTIL_FindHighestSuccessfulTracePoint(const Vector TraceFrom, const Vector TargetPoint, const Vector NextPoint, const float IterationStep, const float MinIdealHeight, const float MaxHeight); // Similar to FindPathToPoint, but you can specify a max acceptable distance for partial results. Will return a failure if it can't reach at least MaxAcceptableDistance away from the ToLocation -dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, const Vector FromLocation, const Vector ToLocation, vector& path, float MaxAcceptableDistance); +dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, const Vector ToLocation, vector& path, float MaxAcceptableDistance); dtStatus FindPathClosestToPoint(const nav_profile& NavProfile, const Vector FromLocation, const Vector ToLocation, vector& path, float MaxAcceptableDistance); dtStatus DEBUG_TestFindPath(const nav_profile& NavProfile, const Vector FromLocation, const Vector ToLocation, vector& path, float MaxAcceptableDistance); @@ -481,6 +484,7 @@ void UTIL_ApplyTempObstaclesToDoor(nav_door* DoorRef, const int Area); nav_door* UTIL_GetNavDoorByEdict(const edict_t* DoorEdict); nav_door* UTIL_GetClosestLiftToPoints(const Vector StartPoint, const Vector EndPoint); +AvHAIOffMeshConnection* UTIL_GetOffMeshConnectionForLift(nav_door* LiftRef); Vector UTIL_AdjustPointAwayFromNavWall(const Vector Location, const float MaxDistanceFromWall); diff --git a/main/source/mod/AvHAIPlayer.cpp b/main/source/mod/AvHAIPlayer.cpp index 6f876c6c..75ff2b60 100644 --- a/main/source/mod/AvHAIPlayer.cpp +++ b/main/source/mod/AvHAIPlayer.cpp @@ -195,7 +195,7 @@ bool BotUseObject(AvHAIPlayer* pBot, edict_t* Target, bool bContinuous) if (AimDot >= 0.95f) { - pBot->Button |= IN_USE; + //pBot->Button |= IN_USE; pBot->LastUseTime = gpGlobals->time; CBaseEntity* UsedObject = CBaseEntity::Instance(Target); @@ -333,8 +333,13 @@ void BotLeap(AvHAIPlayer* pBot, const Vector TargetLocation) float Dot = UTIL_GetDotProduct2D(FaceAngle, MoveDir); - if (Dot >= 0.98f) + if (Dot >= 0.95f) { + // Just give the bot a nudge and make sure they don't miss and end up somewhere they don't want to be + float MoveSpeed = vSize2D(pBot->Edict->v.velocity); + Vector NewVelocity = MoveDir * MoveSpeed; + NewVelocity.z = pBot->Edict->v.velocity.z; + pBot->Button |= IN_ATTACK2; pBot->BotNavInfo.bIsJumping = true; pBot->BotNavInfo.LeapAttemptedTime = gpGlobals->time; @@ -1725,6 +1730,8 @@ void StartNewBotFrame(AvHAIPlayer* pBot) { edict_t* pEdict = pBot->Edict; + if (!pBot->CurrentTask) { pBot->CurrentTask = &pBot->PrimaryBotTask; } + ClearBotInputs(pBot); pBot->CurrentEyePosition = GetPlayerEyePosition(pEdict); @@ -2197,6 +2204,8 @@ void UpdateAIMarinePlayerNSRole(AvHAIPlayer* pBot) void AIPlayerNSThink(AvHAIPlayer* pBot) { + return; + AvHTeam* BotTeam = GetGameRules()->GetTeam(pBot->Player->GetTeam()); if (!BotTeam) { return; } @@ -2676,8 +2685,10 @@ AvHAICombatStrategy GetMarineCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_st float DistToEnemy = vDist2DSq(pBot->Edict->v.origin, CurrentEnemy->LastSeenLocation); + bool bCanRetreat = AITAC_IsCompletedStructureOfTypeNearLocation(BotTeam, (STRUCTURE_MARINE_COMMCHAIR | STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY), ZERO_VECTOR, 0.0f); + // If we are doing something important, don't get distracted by enemies that aren't an immediate threat - if (pBot->CurrentTask && pBot->CurrentTask->TaskType == TASK_DEFEND || pBot->CommanderTask.TaskType != TASK_NONE) + if (pBot->CurrentTask && (pBot->CurrentTask->TaskType == TASK_DEFEND || pBot->CommanderTask.TaskType != TASK_NONE)) { if ((!CurrentEnemy->bHasLOS || DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) && (!vIsZero(pBot->CurrentTask->TaskLocation) && !UTIL_PlayerHasLOSToLocation(CurrentEnemy->EnemyEdict, pBot->CurrentTask->TaskLocation, UTIL_MetresToGoldSrcUnits(30.0f)))) { @@ -2685,7 +2696,7 @@ AvHAICombatStrategy GetMarineCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_st } } - if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_RETREAT) + if (bCanRetreat && pBot->CurrentCombatStrategy == COMBAT_STRATEGY_RETREAT) { int MinDesiredAmmo = imini(UTIL_GetPlayerPrimaryMaxAmmoReserve(pBot->Player), UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player) * 2); @@ -2698,7 +2709,7 @@ AvHAICombatStrategy GetMarineCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_st int NumEnemyAllies = AITAC_GetNumPlayersOnTeamWithLOS(EnemyTeam, EnemyEdict->v.origin, UTIL_MetresToGoldSrcUnits(10.0f), EnemyEdict); int NumFriendlies = AITAC_GetNumPlayersOnTeamWithLOS(BotTeam, pBot->Edict->v.origin, UTIL_MetresToGoldSrcUnits(10.0f), pBot->Edict); - if (CurrentHealthPercent < 0.3f || (CurrentHealthPercent < 0.5f && NumEnemyAllies > 0) || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player)) + if (bCanRetreat && (CurrentHealthPercent < 0.3f || (CurrentHealthPercent < 0.5f && NumEnemyAllies > 0) || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player))) { return COMBAT_STRATEGY_RETREAT; } @@ -2797,8 +2808,6 @@ void AIPlayerNSMarineThink(AvHAIPlayer* pBot) if (MarineCombatThink(pBot)) { return; } } - if (!pBot->CurrentTask) { pBot->CurrentTask = &pBot->PrimaryBotTask; } - if (gpGlobals->time >= pBot->BotNextTaskEvaluationTime) { pBot->BotNextTaskEvaluationTime = gpGlobals->time + frandrange(0.2f, 0.5f); @@ -4199,7 +4208,6 @@ bool AIPlayerMustFinishCurrentTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) void AIPlayerNSAlienThink(AvHAIPlayer* pBot) { - if (!pBot->CurrentTask) { pBot->CurrentTask = &pBot->PrimaryBotTask; } if (pBot->CurrentEnemy > -1) { @@ -4686,8 +4694,6 @@ void AIPlayerCOMarineThink(AvHAIPlayer* pBot) void AIPlayerCOAlienThink(AvHAIPlayer* pBot) { - if (!pBot->CurrentTask) { pBot->CurrentTask = &pBot->PrimaryBotTask; } - AvHMessageID NextCombatUpgrade = GetNextAIPlayerCOAlienUpgrade(pBot); if (NextCombatUpgrade != MESSAGE_NULL) @@ -4762,7 +4768,7 @@ void AIPlayerSetPrimaryCOMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) } // Nothing to attack, just hunt down remaining enemy players. Shouldn't happen in vanilla combat mode, but a plugin might change behaviour - if (FNullEnt(StructureToAttack)) + if (!StructureToAttack || FNullEnt(StructureToAttack)) { vector AllEnemyPlayers = AIMGR_GetAllPlayersOnTeam(EnemyTeam); @@ -5108,7 +5114,7 @@ void AIPlayerSetPrimaryCOAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) } // Nothing to attack, just hunt down remaining enemy players. Shouldn't happen in vanilla combat mode, but a plugin might change behaviour - if (FNullEnt(StructureToAttack)) + if (!StructureToAttack || FNullEnt(StructureToAttack)) { vector AllEnemyPlayers = AIMGR_GetAllPlayersOnTeam(EnemyTeam); edict_t* TargetPlayer = nullptr; @@ -5248,6 +5254,58 @@ void AIPlayerSetSecondaryCOAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) AITASK_ClearBotTask(pBot, Task); } +void AIPlayerEndMatchThink(AvHAIPlayer* pBot) +{ + + pBot->CurrentEnemy = BotGetNextEnemyTarget(pBot); + + if (pBot->CurrentEnemy > -1) + { + if (IsPlayerMarine(pBot->Player)) + { + MarineCombatThink(pBot); + } + else + { + AlienCombatThink(pBot); + } + + return; + } + + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); + + vector EnemyPlayers = AIMGR_GetAllPlayersOnTeam(EnemyTeam); + + AvHPlayer* EnemyToAttack = nullptr; + float MinDist = 0.0f; + + for (auto it = EnemyPlayers.begin(); it != EnemyPlayers.end(); it++) + { + edict_t* PlayerEdict = (*it)->edict(); + if (IsPlayerActiveInGame(PlayerEdict)) + { + float ThisDist = vDist2DSq(pBot->Player->pev->origin, PlayerEdict->v.origin); + + if (!EnemyToAttack || ThisDist < MinDist) + { + EnemyToAttack = (*it); + MinDist = ThisDist; + } + } + } + + if (EnemyToAttack) + { + MoveTo(pBot, EnemyToAttack->pev->origin, MOVESTYLE_NORMAL, UTIL_MetresToGoldSrcUnits(10.0f)); + } + else + { + AIPlayerDMThink(pBot); + } + +} void AIPlayerDMThink(AvHAIPlayer* pBot) { @@ -5267,8 +5325,6 @@ void AIPlayerDMThink(AvHAIPlayer* pBot) return; } - pBot->CurrentTask = &pBot->PrimaryBotTask; - AITASK_BotUpdateAndClearTasks(pBot); if (pBot->CurrentTask->TaskType == TASK_NONE) @@ -5355,8 +5411,17 @@ void AIPlayerThink(AvHAIPlayer* pBot) switch (GetGameRules()->GetMapMode()) { case MAP_MODE_NS: - AIPlayerNSThink(pBot); - break; + { + if (AIMGR_IsMatchPracticallyOver()) + { + AIPlayerEndMatchThink(pBot); + } + else + { + AIPlayerNSThink(pBot); + } + } + break; case MAP_MODE_CO: AIPlayerCOThink(pBot); break; diff --git a/main/source/mod/AvHAIPlayer.h b/main/source/mod/AvHAIPlayer.h index b407e769..4c2e514f 100644 --- a/main/source/mod/AvHAIPlayer.h +++ b/main/source/mod/AvHAIPlayer.h @@ -92,6 +92,9 @@ void AIPlayerCOAlienThink(AvHAIPlayer* pBot); // Think routine for the deathmatch game mode (e.g. when playing CS maps) void AIPlayerDMThink(AvHAIPlayer* pBot); +// What to do when the game hasn't OFFICIALLY ended, but basically is (i.e. one side has no hive/CC/infantry portal) +void AIPlayerEndMatchThink(AvHAIPlayer* pBot); + void TestNavThink(AvHAIPlayer* pBot); void DroneThink(AvHAIPlayer* pBot); void CustomThink(AvHAIPlayer* pBot); diff --git a/main/source/mod/AvHAIPlayerManager.cpp b/main/source/mod/AvHAIPlayerManager.cpp index c1653b97..042b7986 100644 --- a/main/source/mod/AvHAIPlayerManager.cpp +++ b/main/source/mod/AvHAIPlayerManager.cpp @@ -636,6 +636,7 @@ void AIMGR_UpdateAIPlayers() if (UpdateIndex > -1 && BotIndex >= UpdateIndex && NumBotsThinkThisFrame < BotsPerFrame) { AIPlayerThink(bot); + NumBotsThinkThisFrame++; } BotIndex++; @@ -1409,4 +1410,44 @@ bool AIMGR_HasMatchEnded() bool bMatchExceededMaxLength = (GetGameRules()->GetGameTime() > MaxSeconds); return (bMatchExceededMaxLength && AIMGR_GetNumActiveHumanPlayers() == 0); +} + +bool AIMGR_IsMatchPracticallyOver() +{ + if (!GetGameRules()->GetGameStarted() || GetGameRules()->GetMapMode() != MAP_MODE_NS) { return false; } + + AvHTeamNumber TeamANumber = AIMGR_GetTeamANumber(); + AvHTeamNumber TeamBNumber = AIMGR_GetTeamBNumber(); + + if (AIMGR_GetTeamType(TeamANumber) == AVH_CLASS_TYPE_ALIEN) + { + if (AITAC_GetNumTeamHives(TeamANumber, false) == 0) { return true; } + } + else + { + DeployableSearchFilter ChairFilter; + ChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR; + ChairFilter.DeployableTeam = TeamANumber; + ChairFilter.ReachabilityTeam = TeamANumber; + ChairFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + + if (!AITAC_DeployableExistsAtLocation(ZERO_VECTOR, &ChairFilter)) { return true; } + } + + if (AIMGR_GetTeamType(TeamBNumber) == AVH_CLASS_TYPE_ALIEN) + { + if (AITAC_GetNumTeamHives(TeamBNumber, false) == 0) { return true; } + } + else + { + DeployableSearchFilter ChairFilter; + ChairFilter.DeployableTypes = STRUCTURE_MARINE_COMMCHAIR; + ChairFilter.DeployableTeam = TeamBNumber; + ChairFilter.ReachabilityTeam = TeamBNumber; + ChairFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + + if (!AITAC_DeployableExistsAtLocation(ZERO_VECTOR, &ChairFilter)) { return true; } + } + + return false; } \ No newline at end of file diff --git a/main/source/mod/AvHAIPlayerManager.h b/main/source/mod/AvHAIPlayerManager.h index 1057bafe..0be70044 100644 --- a/main/source/mod/AvHAIPlayerManager.h +++ b/main/source/mod/AvHAIPlayerManager.h @@ -124,4 +124,6 @@ void AIMGR_UpdateAISystem(); bool AIMGR_HasMatchEnded(); +bool AIMGR_IsMatchPracticallyOver(); + #endif \ No newline at end of file diff --git a/main/source/mod/AvHConsoleCommands.cpp b/main/source/mod/AvHConsoleCommands.cpp index 46e00c32..ec1faf8f 100644 --- a/main/source/mod/AvHConsoleCommands.cpp +++ b/main/source/mod/AvHConsoleCommands.cpp @@ -1438,9 +1438,8 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++) { AvHAIPlayer* thisBot = (*it); - { - AITASK_SetMoveTask(thisBot, &thisBot->PrimaryBotTask, theAvHPlayer->pev->origin, true); - } + + AITASK_SetMoveTask(thisBot, &thisBot->PrimaryBotTask, theAvHPlayer->pev->origin, true); } theSuccess = true; @@ -1455,6 +1454,101 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) { AIDEBUG_DrawOffMeshConnections(10.0f); + theSuccess = true; + } + else if (FStrEq(pcmd, "bot_evolveskulk")) + { + vector AIPlayers = AIMGR_GetAllAIPlayers(); + + for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++) + { + AvHAIPlayer* thisBot = (*it); + + if (!IsPlayerAlien(thisBot->Edict) || !IsPlayerActiveInGame(thisBot->Edict)) { continue; } + + AITASK_SetEvolveTask(thisBot, &thisBot->PrimaryBotTask, thisBot->CurrentFloorPosition, ALIEN_LIFEFORM_ONE, true); + } + + theSuccess = true; + } + else if (FStrEq(pcmd, "bot_evolvegorge")) + { + vector AIPlayers = AIMGR_GetAllAIPlayers(); + + for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++) + { + AvHAIPlayer* thisBot = (*it); + + if (!IsPlayerAlien(thisBot->Edict) || !IsPlayerActiveInGame(thisBot->Edict)) { continue; } + + if (thisBot->Player->GetResources() < BALANCE_VAR(kGorgeCost)) + { + thisBot->Player->GiveResources(BALANCE_VAR(kGorgeCost)); + } + + AITASK_SetEvolveTask(thisBot, &thisBot->PrimaryBotTask, thisBot->CurrentFloorPosition, ALIEN_LIFEFORM_TWO, true); + } + + theSuccess = true; + } + else if (FStrEq(pcmd, "bot_evolvelerk")) + { + vector AIPlayers = AIMGR_GetAllAIPlayers(); + + for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++) + { + AvHAIPlayer* thisBot = (*it); + + if (!IsPlayerAlien(thisBot->Edict) || !IsPlayerActiveInGame(thisBot->Edict)) { continue; } + + if (thisBot->Player->GetResources() < BALANCE_VAR(kLerkCost)) + { + thisBot->Player->GiveResources(BALANCE_VAR(kLerkCost)); + } + + AITASK_SetEvolveTask(thisBot, &thisBot->PrimaryBotTask, thisBot->CurrentFloorPosition, ALIEN_LIFEFORM_THREE, true); + } + + theSuccess = true; + } + else if (FStrEq(pcmd, "bot_evolvefade")) + { + vector AIPlayers = AIMGR_GetAllAIPlayers(); + + for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++) + { + AvHAIPlayer* thisBot = (*it); + + if (!IsPlayerAlien(thisBot->Edict) || !IsPlayerActiveInGame(thisBot->Edict)) { continue; } + + if (thisBot->Player->GetResources() < BALANCE_VAR(kFadeCost)) + { + thisBot->Player->GiveResources(BALANCE_VAR(kFadeCost)); + } + + AITASK_SetEvolveTask(thisBot, &thisBot->PrimaryBotTask, thisBot->CurrentFloorPosition, ALIEN_LIFEFORM_FOUR, true); + } + + theSuccess = true; + } + else if (FStrEq(pcmd, "bot_evolveonos")) + { + vector AIPlayers = AIMGR_GetAllAIPlayers(); + + for (auto it = AIPlayers.begin(); it != AIPlayers.end(); it++) + { + AvHAIPlayer* thisBot = (*it); + + if (!IsPlayerAlien(thisBot->Edict) || !IsPlayerActiveInGame(thisBot->Edict)) { continue; } + + if (thisBot->Player->GetResources() < BALANCE_VAR(kOnosCost)) + { + thisBot->Player->GiveResources(BALANCE_VAR(kOnosCost)); + } + + AITASK_SetEvolveTask(thisBot, &thisBot->PrimaryBotTask, thisBot->CurrentFloorPosition, ALIEN_LIFEFORM_FIVE, true); + } + theSuccess = true; } else if( FStrEq( pcmd, kcRemoveUpgrade) )