diff --git a/main/navmeshes/ns_eclipse.nav b/main/navmeshes/ns_eclipse.nav index f25245fe..0142be3c 100644 Binary files a/main/navmeshes/ns_eclipse.nav and b/main/navmeshes/ns_eclipse.nav differ diff --git a/main/nsbots.ini b/main/nsbots.ini index 45d1ce43..c5ce194d 100644 --- a/main/nsbots.ini +++ b/main/nsbots.ini @@ -30,7 +30,7 @@ MarineReactionTime=0.4 MarineAimSkill=0.1 MarineMovementTracking=0.1 MarineViewSpeed=0.5 -AlienReactionTime=0.4 +AlienReactionTime=0.5 AlienAimSkill=0.2 AlienMovementTracking=0.2 AlienViewSpeed=0.75 @@ -89,4 +89,4 @@ TeamSize=co_kestrel:5/5 # ChamberSequence:movement/?/? # Or if you want sensory always last, but movement and defence random, use # ChamberSequence=?/?/sensory -ChamberSequence=defense/movement/sensory +ChamberSequence=?/?/? diff --git a/main/source/dlls/hl.vcxproj b/main/source/dlls/hl.vcxproj index e4d5c8ec..cd791f1a 100644 --- a/main/source/dlls/hl.vcxproj +++ b/main/source/dlls/hl.vcxproj @@ -256,6 +256,7 @@ + @@ -1390,6 +1391,7 @@ + diff --git a/main/source/dlls/hl.vcxproj.filters b/main/source/dlls/hl.vcxproj.filters index 90e9f2a5..3a28518f 100644 --- a/main/source/dlls/hl.vcxproj.filters +++ b/main/source/dlls/hl.vcxproj.filters @@ -545,6 +545,9 @@ mod\bots + + mod\bots + @@ -967,5 +970,8 @@ mod\bots + + mod\bots + \ No newline at end of file diff --git a/main/source/dlls/sound.cpp b/main/source/dlls/sound.cpp index 1c172c72..10f8f867 100644 --- a/main/source/dlls/sound.cpp +++ b/main/source/dlls/sound.cpp @@ -25,6 +25,10 @@ #include "gamerules.h" #include "../mod/AvHSpecials.h" +#ifdef AVH_SERVER +#include "../mod/AvHAIPlayerManager.h" +#endif + static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ); @@ -1429,7 +1433,9 @@ void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volu ALERT( at_aiconsole, "Unable to find %s in sentences.txt\n", sample ); } else + { EMIT_SOUND_DYN2(entity, channel, sample, volume, attenuation, flags, pitch); + } } // play a specific sentence over the HEV suit speaker - just pass player entity, and !sentencename diff --git a/main/source/mod/AvHAIConfig.cpp b/main/source/mod/AvHAIConfig.cpp index cf39cbd4..36822cf0 100644 --- a/main/source/mod/AvHAIConfig.cpp +++ b/main/source/mod/AvHAIConfig.cpp @@ -718,7 +718,7 @@ void CONFIG_RegenerateIniFile() fprintf(NewConfigFile, "# ChamberSequence:movement/?/?\n"); fprintf(NewConfigFile, "# Or if you want sensory always last, but movement and defence random, use\n"); fprintf(NewConfigFile, "# ChamberSequence=?/?/sensory\n"); - fprintf(NewConfigFile, "ChamberSequence=defense/movement/sensory\n"); + fprintf(NewConfigFile, "ChamberSequence=?/?/?\n"); fflush(NewConfigFile); fclose(NewConfigFile); diff --git a/main/source/mod/AvHAIConstants.h b/main/source/mod/AvHAIConstants.h index 00946ad8..2cbb9e34 100644 --- a/main/source/mod/AvHAIConstants.h +++ b/main/source/mod/AvHAIConstants.h @@ -497,6 +497,7 @@ typedef struct _ENEMY_STATUS float NextUpdateTime = 0.0f; // When the bot can next react to a change in target's state float NextVelocityUpdateTime = 0.0f; // When the bot can next react to a change in target's state float EndTrackingTime = 0.0f; // When to stop "sensing" enemy movement after losing LOS + float CertaintyOfLocation = 0.0f; // How sure the bot is where the enemy is if they're cloaked } enemy_status; @@ -804,6 +805,8 @@ typedef struct AVH_AI_PLAYER float ServerUpdateDelta = 0.0f; // How long since we last called RunPlayerMove float LastServerUpdateTime = 0.0f; // When we last called RunPlayerMove + float HearingThreshold = 0.0f; // How loud does a sound need to be before the bot detects it? This is set when hearing a sound so that louder sounds drown out quieter ones, and decrements quickly + } AvHAIPlayer; diff --git a/main/source/mod/AvHAINavigation.cpp b/main/source/mod/AvHAINavigation.cpp index ac8e2594..9882d724 100644 --- a/main/source/mod/AvHAINavigation.cpp +++ b/main/source/mod/AvHAINavigation.cpp @@ -2059,7 +2059,7 @@ dtStatus FindPathClosestToPoint(AvHAIPlayer* pBot, const BotMoveStyle MoveStyle, Vector FromFloorLocation = AdjustPointForPathfinding(FromLocation); - nav_door* LiftReference = UTIL_GetNavDoorByEdict(pBot->Edict->v.groundentity); + nav_door* LiftReference = UTIL_GetLiftReferenceByEdict(pBot->Edict->v.groundentity); bool bMustDisembarkLiftFirst = false; Vector LiftStart = ZERO_VECTOR; Vector LiftEnd = ZERO_VECTOR; @@ -3691,11 +3691,16 @@ void StructureBlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vect void BlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) { - Vector vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint); + Vector vForward = UTIL_GetVectorNormal2D(EndPoint - pBot->Edict->v.origin); if (vIsZero(vForward)) { - vForward = UTIL_GetForwardVector2D(pBot->Edict->v.angles); + vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint); + + if (vIsZero(vForward)) + { + vForward = UTIL_GetForwardVector2D(pBot->Edict->v.angles); + } } pBot->desiredMovementDir = vForward; @@ -3713,6 +3718,10 @@ void BlockedMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoi if (FaceDot < 0.95f) { float MoveSpeed = vSize2D(pBot->Edict->v.velocity); + if (MoveSpeed < 20.0f) + { + MoveSpeed = 100.0f; + } Vector NewVelocity = vForward * MoveSpeed; NewVelocity.z = pBot->Edict->v.velocity.z; @@ -3729,6 +3738,11 @@ void JumpMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) if (vIsZero(vForward)) { vForward = UTIL_GetVectorNormal2D(EndPoint - StartPoint); + + if (vIsZero(vForward)) + { + vForward = UTIL_GetForwardVector2D(pBot->Edict->v.angles); + } } pBot->desiredMovementDir = vForward; @@ -3746,6 +3760,10 @@ void JumpMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector EndPoint) if (FaceDot < 0.95f) { float MoveSpeed = vSize2D(pBot->Edict->v.velocity); + if (MoveSpeed < 20.0f) + { + MoveSpeed = 100.0f; + } Vector NewVelocity = vForward * MoveSpeed; NewVelocity.z = pBot->Edict->v.velocity.z; @@ -4729,7 +4747,7 @@ void BlinkClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector End if (GetPlayerCurrentWeapon(pBot->Player) != WEAPON_FADE_BLINK) { return; } // Only blink if we're below the target climb height - if (pEdict->v.origin.z < RequiredClimbHeight + 32.0f) + if (pEdict->v.origin.z < RequiredClimbHeight + 4.0f) { float HeightToClimb = (fabsf(pEdict->v.origin.z - RequiredClimbHeight)); @@ -4746,6 +4764,12 @@ void BlinkClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector End if (FaceDot < 0.95f) { float MoveSpeed = vSize2D(pBot->Edict->v.velocity); + + if (MoveSpeed < 20.0f) + { + MoveSpeed = 100.0f; + } + Vector NewVelocity = MoveDir * MoveSpeed; NewVelocity.z = pBot->Edict->v.velocity.z; @@ -6365,6 +6389,7 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move { pBot->BotNavInfo.StuckInfo.bPathFollowFailed = false; ClearBotMovement(pBot); + return true; } @@ -6811,6 +6836,7 @@ void BotFollowSwimPath(AvHAIPlayer* pBot) if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.CurrentPathPoint >= pBot->BotNavInfo.CurrentPath.size()) { ClearBotPath(pBot); + NAV_ClearMovementTask(pBot); return; } @@ -6964,6 +6990,7 @@ void BotFollowPath(AvHAIPlayer* pBot) MoveToWithoutNav(pBot, CurrentNode.Location); pBot->BotNavInfo.StuckInfo.bPathFollowFailed = true; ClearBotPath(pBot); + NAV_ClearMovementTask(pBot); return; } @@ -7408,6 +7435,7 @@ void ClearBotStuck(AvHAIPlayer* pBot) bool BotRecalcPath(AvHAIPlayer* pBot, const Vector Destination) { ClearBotPath(pBot); + NAV_ClearMovementTask(pBot); Vector ValidNavmeshPoint = UTIL_ProjectPointToNavmesh(Destination, Vector(max_ai_use_reach, max_ai_use_reach, max_ai_use_reach), pBot->BotNavInfo.NavProfile); @@ -8772,6 +8800,18 @@ void UTIL_PopulateDoors() NavDoors.push_back(NewDoor); } + for (auto it = BaseMapConnections.begin(); it != BaseMapConnections.end(); it++) + { + if (!(it->ConnectionFlags & SAMPLE_POLYFLAGS_LIFT)) { continue; } + + nav_door* CorrespondingLift = UTIL_GetClosestLiftToPoints(it->FromLocation, it->ToLocation); + + if (CorrespondingLift) + { + it->TargetObject = CorrespondingLift->DoorEdict; + } + } + UTIL_UpdateDoors(true); } @@ -8790,6 +8830,28 @@ nav_door* UTIL_GetNavDoorByEdict(const edict_t* DoorEdict) return nullptr; } +nav_door* UTIL_GetLiftReferenceByEdict(const edict_t* DoorEdict) +{ + if (FNullEnt(DoorEdict)) { return nullptr; } + + for (auto it = NavDoors.begin(); it != NavDoors.end(); it++) + { + if (it->DoorEdict == DoorEdict) + { + if (UTIL_GetOffMeshConnectionForLift(&(*it)) != nullptr) + { + return &(*it); + } + else + { + return nullptr; + } + } + } + + return nullptr; +} + AvHAIOffMeshConnection* UTIL_GetOffMeshConnectionForLift(nav_door* LiftRef) { if (!LiftRef) { return nullptr; } @@ -8801,15 +8863,20 @@ AvHAIOffMeshConnection* UTIL_GetOffMeshConnectionForLift(nav_door* LiftRef) { 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) + if (it->TargetObject == LiftRef->DoorEdict) { - NearestConnection = &(*it); - MinDist = ThisDist; + return &(*it); } + + //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; diff --git a/main/source/mod/AvHAINavigation.h b/main/source/mod/AvHAINavigation.h index 0966f9c6..698c91b8 100644 --- a/main/source/mod/AvHAINavigation.h +++ b/main/source/mod/AvHAINavigation.h @@ -482,6 +482,7 @@ void UTIL_PopulateWeldableObstacles(); void UTIL_ApplyTempObstaclesToDoor(nav_door* DoorRef, const int Area); +nav_door* UTIL_GetLiftReferenceByEdict(const edict_t* DoorEdict); nav_door* UTIL_GetNavDoorByEdict(const edict_t* DoorEdict); nav_door* UTIL_GetClosestLiftToPoints(const Vector StartPoint, const Vector EndPoint); AvHAIOffMeshConnection* UTIL_GetOffMeshConnectionForLift(nav_door* LiftRef); diff --git a/main/source/mod/AvHAIPlayer.cpp b/main/source/mod/AvHAIPlayer.cpp index 7376e545..9f5ecdee 100644 --- a/main/source/mod/AvHAIPlayer.cpp +++ b/main/source/mod/AvHAIPlayer.cpp @@ -1353,6 +1353,9 @@ void BotUpdateView(AvHAIPlayer* pBot) UpdateAIPlayerViewFrustum(pBot); + float ViewUpdateDelta = gpGlobals->time - pBot->LastViewUpdateTime; + ViewUpdateDelta = clampf(ViewUpdateDelta, 0.0f, 0.2f); + // Update list of currently visible players for (int i = 1; i <= gpGlobals->maxClients; i++) { @@ -1378,6 +1381,10 @@ void BotUpdateView(AvHAIPlayer* pBot) enemy_status* TrackingInfo = &pBot->TrackedEnemies[EnemyIndex]; + + TrackingInfo->CertaintyOfLocation -= (ViewUpdateDelta * 0.15f); + TrackingInfo->CertaintyOfLocation = clampf(TrackingInfo->CertaintyOfLocation, 0.0f, 1.0f); + if (gpGlobals->time < TrackingInfo->NextUpdateTime) { continue; @@ -1434,6 +1441,7 @@ void BotUpdateView(AvHAIPlayer* pBot) if (bInFOV && (bCanSeeEnemy || bIsTracked)) { + TrackingInfo->CertaintyOfLocation = 1.0f; Vector FloorLocation = UTIL_GetEntityGroundLocation(Enemy); Vector BotVelocity = Enemy->v.velocity; @@ -1486,7 +1494,7 @@ void BotUpdateView(AvHAIPlayer* pBot) } } - if (bHasLOS) + if (bHasLOS && bCanSeeEnemy) { TrackingInfo->LastLOSPosition = pBot->CurrentFloorPosition + Vector(0.0f, 0.0f, 5.0f); @@ -1554,6 +1562,8 @@ void BotUpdateView(AvHAIPlayer* pBot) { pBot->LastSafeLocation = pBot->Edict->v.origin; } + + } bool UTIL_IsCloakedPlayerInvisible(edict_t* Observer, AvHPlayer* Player) @@ -1595,6 +1605,7 @@ void BotClearEnemyTrackingInfo(enemy_status* TrackingInfo) TrackingInfo->LastSeenTime = 0.0f; TrackingInfo->LastLOSPosition = ZERO_VECTOR; TrackingInfo->LastHiddenPosition = ZERO_VECTOR; + TrackingInfo->CertaintyOfLocation = 0.0f; } void UpdateAIPlayerViewFrustum(AvHAIPlayer* pBot) @@ -1895,23 +1906,25 @@ void EndBotFrame(AvHAIPlayer* pBot) void CustomThink(AvHAIPlayer* pBot) { - if (IsPlayerMarine(pBot->Edict)) { return; } - - if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_SENSORY)) - { - BotEvolveUpgrade(pBot, pBot->CurrentFloorPosition, ALIEN_EVOLUTION_TEN); - return; - } pBot->CurrentEnemy = BotGetNextEnemyTarget(pBot); - if (pBot->CurrentEnemy < 0) + if (pBot->CurrentEnemy >= 0) { - MoveTo(pBot, AITAC_GetTeamStartingLocation(AIMGR_GetEnemyTeam(pBot->Player->GetTeam())), MOVESTYLE_NORMAL); - } - else - { - AlienCombatThink(pBot); + enemy_status* TrackingInfo = &pBot->TrackedEnemies[pBot->CurrentEnemy]; + + char msg[32]; + sprintf(msg, "%.2f\n", TrackingInfo->CertaintyOfLocation); + UTIL_SayText(msg, CBaseEntity::Instance(INDEXENT(1))); + + if (IsPlayerMarine(pBot->Edict)) + { + MarineCombatThink(pBot); + } + else + { + AlienCombatThink(pBot); + } } } @@ -2005,11 +2018,41 @@ void UpdateAIPlayerDMRole(AvHAIPlayer* pBot) } +void AIPlayerHearEnemy(AvHAIPlayer* pBot, edict_t* HeardEnemy, float SoundVolume) +{ + int heardIndex = ENTINDEX(HeardEnemy) - 1; + + if (heardIndex < 0 || heardIndex >= 32 || HeardEnemy->v.team == pBot->Edict->v.team) { return; } + + enemy_status* HeardEnemyStatus = &pBot->TrackedEnemies[heardIndex]; + + HeardEnemyStatus->LastSeenTime = gpGlobals->time; + HeardEnemyStatus->CertaintyOfLocation += SoundVolume; + HeardEnemyStatus->CertaintyOfLocation = clampf(HeardEnemyStatus->CertaintyOfLocation, 0.0f, 1.0f); + + if (HeardEnemyStatus->CertaintyOfLocation < 0.15f) { return; } + + // If the bot can't see the enemy (bCurrentlyVisible is false) then set the last seen location to a random point in the vicinity so the bot doesn't immediately know where they are + if (HeardEnemyStatus->bIsVisible || HeardEnemyStatus->CertaintyOfLocation > 0.75f || vDist2DSq(HeardEnemyStatus->EnemyEdict->v.origin, pBot->Edict->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(3.0f))) + { + HeardEnemyStatus->LastSeenLocation = HeardEnemy->v.origin; + } + else + { + // The further the enemy is, the more inaccurate the bot's guess will be where they are + HeardEnemyStatus->LastSeenLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(SKULK_BASE_NAV_PROFILE), HeardEnemy->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + } + + HeardEnemyStatus->bIsAwareOfPlayer = true; +} + void AIPlayerTakeDamage(AvHAIPlayer* pBot, int damageTaken, edict_t* aggressor) { int aggressorIndex = ENTINDEX(aggressor) - 1; - if (aggressorIndex > -1 && aggressor->v.team != pBot->Edict->v.team && IsPlayerActiveInGame(aggressor)) + if (aggressorIndex < 0 || aggressorIndex >= 32) { return; } + + if (aggressor->v.team != pBot->Edict->v.team && IsPlayerActiveInGame(aggressor)) { pBot->TrackedEnemies[aggressorIndex].LastSeenTime = gpGlobals->time; @@ -2575,7 +2618,7 @@ AvHAICombatStrategy GetFadeCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_stat { if (DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { - if ((bEnemyHasDeadlyWeapon && (FacingDot > 0.5f || NumAllies > 0)) || NumAllies > 2) + if ((bEnemyHasDeadlyWeapon && (FacingDot > 0.5f || NumAllies > 0)) || NumAllies > 1) { return COMBAT_STRATEGY_SKIRMISH; } @@ -2583,7 +2626,7 @@ AvHAICombatStrategy GetFadeCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_stat } else { - if ((bEnemyHasDeadlyWeapon && (FacingDot > 0.5f || NumAllies > 0)) || NumAllies > 2) + if ((bEnemyHasDeadlyWeapon && (FacingDot > 0.5f || NumAllies > 0)) || NumAllies > 1) { Vector EnemyVelocity = UTIL_GetVectorNormal2D(CurrentEnemy->LastSeenVelocity); @@ -2683,7 +2726,7 @@ 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); + bool bCanRetreat = AITAC_IsCompletedStructureOfTypeNearLocation(BotTeam, (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)) @@ -2712,6 +2755,11 @@ AvHAICombatStrategy GetMarineCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_st return COMBAT_STRATEGY_RETREAT; } + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) == 0 && UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) == 0 && UTIL_GetPlayerSecondaryWeaponClipAmmo(pBot->Player) == 0 && UTIL_GetPlayerSecondaryAmmoReserve(pBot->Player) == 0) + { + return COMBAT_STRATEGY_ATTACK; + } + // Shotty users should attack, can't really skirmish with a shotgun if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_SHOTGUN) && (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0 || UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0)) { @@ -2899,6 +2947,7 @@ void BotThrowGrenadeAtTarget(AvHAIPlayer* pBot, const Vector TargetPoint) BotShootLocation(pBot, GetPlayerCurrentWeapon(pBot->Player), ThrowTargetLocation); } + bool BombardierCombatThink(AvHAIPlayer* pBot) { return false; @@ -2921,11 +2970,12 @@ bool RegularMarineCombatThink(AvHAIPlayer* pBot) bool bBotIsGrenadier = (DesiredCombatWeapon == WEAPON_MARINE_GL); - float DistToEnemy = vDist2DSq(pBot->Edict->v.origin, CurrentEnemy->v.origin); + float DistToEnemy = vDist2DSq(pBot->Edict->v.origin, TrackedEnemyRef->LastSeenLocation); bool bEnemyIsRanged = IsPlayerMarine(TrackedEnemyRef->EnemyPlayer) || ((GetPlayerCurrentWeapon(TrackedEnemyRef->EnemyPlayer) == WEAPON_FADE_ACIDROCKET) && DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))); float LastEnemySeenTime = (TrackedEnemyRef->LastTrackedTime > 0.0f) ? TrackedEnemyRef->LastTrackedTime : TrackedEnemyRef->LastSeenTime; + float TimeSinceLastEnemySighting = gpGlobals->time - LastEnemySeenTime; Vector LastEnemySeenLocation = TrackedEnemyRef->LastSeenLocation; // Run away and restock @@ -3017,15 +3067,29 @@ bool RegularMarineCombatThink(AvHAIPlayer* pBot) // Maintain distance, pop and shoot if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_SKIRMISH || pBot->CurrentCombatStrategy == COMBAT_STRATEGY_AMBUSH) { + pBot->DesiredCombatWeapon = DesiredCombatWeapon; + + if (GetPlayerCurrentWeapon(pBot->Player) != DesiredCombatWeapon) + { + return true; + } + if (vIsZero(pBot->LastSafeLocation)) { pBot->LastSafeLocation = AITAC_GetTeamStartingLocation(BotTeam); } - if (TrackedEnemyRef->bHasLOS) + float DesiredDistance = GetMinIdealWeaponRange(DesiredCombatWeapon) + ((GetMaxIdealWeaponRange(DesiredCombatWeapon) - GetMinIdealWeaponRange(DesiredCombatWeapon)) * 0.5f); + + bool bCanReloadCurrentWeapon = (WeaponCanBeReloaded(DesiredCombatWeapon) && GetPlayerCurrentWeaponClipAmmo(pBot->Player) < GetPlayerCurrentWeaponMaxClipAmmo(pBot->Player) && GetPlayerCurrentWeaponReserveAmmo(pBot->Player) > 0); + bool bMustReloadCurrentWeapon = bCanReloadCurrentWeapon && GetPlayerCurrentWeaponClipAmmo(pBot->Player) == 0; + bool bCanReloadAnyWeapon = BotAnyWeaponNeedsReloading(pBot); + + if (TrackedEnemyRef->bHasLOS && (TrackedEnemyRef->bIsVisible || TrackedEnemyRef->CertaintyOfLocation >= 0.8f)) { - if (GetPlayerCurrentWeaponClipAmmo(pBot->Player) == 0) + if (bMustReloadCurrentWeapon) { + BotLookAt(pBot, TrackedEnemyRef->LastSeenLocation); MoveTo(pBot, pBot->LastSafeLocation, MOVESTYLE_NORMAL); BotReloadWeapons(pBot); return true; @@ -3033,7 +3097,9 @@ bool RegularMarineCombatThink(AvHAIPlayer* pBot) if (vDist2DSq(pBot->Edict->v.origin, pBot->LastSafeLocation) > sqrf(UTIL_MetresToGoldSrcUnits(3.0f))) { + BotLookAt(pBot, TrackedEnemyRef->LastSeenLocation); MoveTo(pBot, pBot->LastSafeLocation, MOVESTYLE_NORMAL); + } else { @@ -3099,6 +3165,7 @@ bool RegularMarineCombatThink(AvHAIPlayer* pBot) if (BotReloadWeapons(pBot)) { return true; } + BotLookAt(pBot, LastEnemySeenLocation); MoveTo(pBot, LastEnemySeenLocation, MOVESTYLE_NORMAL); } @@ -3108,19 +3175,43 @@ bool RegularMarineCombatThink(AvHAIPlayer* pBot) // Go for the kill. Maintain desired distance and pursue when needed if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_ATTACK) { - AvHAIWeapon IdealAttackWeapon = (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0 || UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0) ? UTIL_GetPlayerPrimaryWeapon(pBot->Player) : DesiredCombatWeapon; + pBot->DesiredCombatWeapon = DesiredCombatWeapon; - float DesiredDistance = GetMinIdealWeaponRange(IdealAttackWeapon) + ((GetMaxIdealWeaponRange(IdealAttackWeapon) - GetMinIdealWeaponRange(IdealAttackWeapon)) * 0.5f); - - bool bCanReloadCurrentWeapon = (WeaponCanBeReloaded(DesiredCombatWeapon) && GetPlayerCurrentWeaponClipAmmo(pBot->Player) < GetPlayerCurrentWeaponMaxClipAmmo(pBot->Player) && GetPlayerCurrentWeaponReserveAmmo(pBot->Player) > 0); - bool bMustReloadCurrentWeapon = bCanReloadCurrentWeapon && GetPlayerCurrentWeaponClipAmmo(pBot->Player) == 0; + if (GetPlayerCurrentWeapon(pBot->Player) != DesiredCombatWeapon) + { + return true; + } if (vIsZero(pBot->LastSafeLocation)) { pBot->LastSafeLocation = AITAC_GetTeamStartingLocation(BotTeam); } - if (!TrackedEnemyRef->bHasLOS) + float DesiredDistance = GetMinIdealWeaponRange(DesiredCombatWeapon) + ((GetMaxIdealWeaponRange(DesiredCombatWeapon) - GetMinIdealWeaponRange(DesiredCombatWeapon)) * 0.5f); + + bool bCanReloadCurrentWeapon = (WeaponCanBeReloaded(DesiredCombatWeapon) && GetPlayerCurrentWeaponClipAmmo(pBot->Player) < GetPlayerCurrentWeaponMaxClipAmmo(pBot->Player) && GetPlayerCurrentWeaponReserveAmmo(pBot->Player) > 0); + bool bMustReloadCurrentWeapon = bCanReloadCurrentWeapon && GetPlayerCurrentWeaponClipAmmo(pBot->Player) == 0; + bool bCanReloadAnyWeapon = BotAnyWeaponNeedsReloading(pBot); + + if (bMustReloadCurrentWeapon) + { + BotReloadWeapons(pBot); + BotLookAt(pBot, TrackedEnemyRef->LastSeenLocation); + + if (TrackedEnemyRef->bHasLOS || DistToEnemy < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) + { + MoveTo(pBot, pBot->LastSafeLocation, MOVESTYLE_NORMAL); + } + else + { + BotGuardLocation(pBot, pBot->Edict->v.origin); + } + + return true; + } + + + if (!TrackedEnemyRef->bHasLOS || (!TrackedEnemyRef->bIsVisible && TrackedEnemyRef->CertaintyOfLocation < 0.8f)) { if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GRENADE) || (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GL) && UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0)) { @@ -3133,103 +3224,100 @@ bool RegularMarineCombatThink(AvHAIPlayer* pBot) } } - if ((IdealAttackWeapon != DesiredCombatWeapon || bCanReloadCurrentWeapon) && gpGlobals->time - TrackedEnemyRef->LastSeenTime > 3.0f) + if (bCanReloadAnyWeapon && gpGlobals->time - TrackedEnemyRef->LastSeenTime > 3.0f) { BotReloadWeapons(pBot); - if (vDist2DSq(pBot->Edict->v.origin, TrackedEnemyRef->LastVisibleLocation) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) - { + BotLookAt(pBot, TrackedEnemyRef->LastSeenLocation); + + if (DistToEnemy < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) + { MoveTo(pBot, AITAC_GetTeamStartingLocation(BotTeam), MOVESTYLE_NORMAL); } return true; } - MoveTo(pBot, TrackedEnemyRef->LastSeenLocation, MOVESTYLE_NORMAL); + BotGuardLocation(pBot, LastEnemySeenLocation); return true; } + if (bCanReloadCurrentWeapon && DistToEnemy > sqrf(DesiredDistance)) + { + BotReloadWeapons(pBot); + } + BotAttackResult LOSCheck = PerformAttackLOSCheck(pBot, DesiredCombatWeapon, CurrentEnemy); - if (bMustReloadCurrentWeapon) + if (LOSCheck != ATTACK_SUCCESS) { - MoveTo(pBot, pBot->LastSafeLocation, MOVESTYLE_NORMAL); - BotReloadWeapons(pBot); + MoveTo(pBot, TrackedEnemyRef->LastSeenLocation, MOVESTYLE_NORMAL); return true; } - if (DistToEnemy > sqrf(DesiredDistance)) + if (bEnemyIsRanged) { - if (IdealAttackWeapon != DesiredCombatWeapon) + Vector EnemyOrientation = UTIL_GetVectorNormal2D(CurrentEnemy->v.origin - pBot->Edict->v.origin); + + Vector RightDir = UTIL_GetCrossProduct(EnemyOrientation, UP_VECTOR); + + pBot->desiredMovementDir = (pBot->BotNavInfo.bZig) ? UTIL_GetVectorNormal2D(RightDir) : UTIL_GetVectorNormal2D(-RightDir); + + // Let's get ziggy with it + if (gpGlobals->time > pBot->BotNavInfo.NextZigTime) { - BotReloadWeapons(pBot); - MoveTo(pBot, pBot->LastSafeLocation, MOVESTYLE_NORMAL); - return true; + pBot->BotNavInfo.bZig = !pBot->BotNavInfo.bZig; + pBot->BotNavInfo.NextZigTime = gpGlobals->time + frandrange(0.5f, 1.0f); } - MoveTo(pBot, LastEnemySeenLocation, MOVESTYLE_NORMAL); - + BotMovementInputs(pBot); } else { - if (bEnemyIsRanged) + float MinDesiredDist = GetMinIdealWeaponRange(DesiredCombatWeapon); + Vector Orientation = UTIL_GetVectorNormal2D(CurrentEnemy->v.origin - pBot->Edict->v.origin); + + float EnemyMoveDot = UTIL_GetDotProduct2D(UTIL_GetVectorNormal2D(CurrentEnemy->v.velocity), -Orientation); + + // Enemy is too close for comfort, or is moving towards us. Back up + if (DistToEnemy < MinDesiredDist || EnemyMoveDot > 0.7f) { - Vector EnemyOrientation = UTIL_GetVectorNormal2D(CurrentEnemy->v.origin - pBot->Edict->v.origin); + Vector RetreatLocation = pBot->CurrentFloorPosition - (Orientation * 50.0f); - Vector RightDir = UTIL_GetCrossProduct(EnemyOrientation, UP_VECTOR); - - pBot->desiredMovementDir = (pBot->BotNavInfo.bZig) ? UTIL_GetVectorNormal2D(RightDir) : UTIL_GetVectorNormal2D(-RightDir); - - // Let's get ziggy with it - if (gpGlobals->time > pBot->BotNavInfo.NextZigTime) + if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, RetreatLocation)) { - pBot->BotNavInfo.bZig = !pBot->BotNavInfo.bZig; - pBot->BotNavInfo.NextZigTime = gpGlobals->time + frandrange(0.5f, 1.0f); + MoveDirectlyTo(pBot, RetreatLocation); + } + + if (DesiredCombatWeapon != WEAPON_MARINE_KNIFE) + { + if (DistToEnemy < sqrf(100.0f)) + { + if (IsPlayerReloading(pBot->Player) && CanInterruptWeaponReload(GetPlayerCurrentWeapon(pBot->Player)) && GetPlayerCurrentWeaponClipAmmo(pBot->Player) > 0) + { + InterruptReload(pBot); + return true; + } + BotJump(pBot); + } } - BotMovementInputs(pBot); } else { - - float MinDesiredDist = GetMinIdealWeaponRange(DesiredCombatWeapon); - Vector Orientation = UTIL_GetVectorNormal2D(CurrentEnemy->v.origin - pBot->Edict->v.origin); - - float EnemyMoveDot = UTIL_GetDotProduct2D(UTIL_GetVectorNormal2D(CurrentEnemy->v.velocity), -Orientation); - - // Enemy is too close for comfort, or is moving towards us. Back up - if (DistToEnemy < MinDesiredDist || EnemyMoveDot > 0.7f) - { - Vector RetreatLocation = pBot->CurrentFloorPosition - (Orientation * 50.0f); - - if (UTIL_PointIsDirectlyReachable(pBot->CurrentFloorPosition, RetreatLocation)) - { - MoveDirectlyTo(pBot, RetreatLocation); - } - - if (DesiredCombatWeapon != WEAPON_MARINE_KNIFE) - { - if (DistToEnemy < sqrf(100.0f)) - { - if (IsPlayerReloading(pBot->Player) && CanInterruptWeaponReload(GetPlayerCurrentWeapon(pBot->Player)) && GetPlayerCurrentWeaponClipAmmo(pBot->Player) > 0) - { - InterruptReload(pBot); - return true; - } - BotJump(pBot); - } - } - - } - else + if (!UTIL_PlayerHasLOSToLocation(pBot->Edict, TrackedEnemyRef->LastSeenLocation, UTIL_MetresToGoldSrcUnits(5.0f))) { MoveTo(pBot, TrackedEnemyRef->LastSeenLocation, MOVESTYLE_NORMAL); } + else + { + BotGuardLocation(pBot, TrackedEnemyRef->LastSeenLocation); + } } - - BotShootTarget(pBot, DesiredCombatWeapon, CurrentEnemy); } + BotShootTarget(pBot, DesiredCombatWeapon, CurrentEnemy); + return true; } diff --git a/main/source/mod/AvHAIPlayer.h b/main/source/mod/AvHAIPlayer.h index 4c2e514f..521cf1a9 100644 --- a/main/source/mod/AvHAIPlayer.h +++ b/main/source/mod/AvHAIPlayer.h @@ -153,6 +153,7 @@ void UpdateAIPlayerDMRole(AvHAIPlayer* pBot); bool ShouldAIPlayerTakeCommand(AvHAIPlayer* pBot); void AIPlayerTakeDamage(AvHAIPlayer* pBot, int damageTaken, edict_t* aggressor); +void AIPlayerHearEnemy(AvHAIPlayer* pBot, edict_t* HeardEnemy, float SoundVolume); int BotGetNextEnemyTarget(AvHAIPlayer* pBot); diff --git a/main/source/mod/AvHAIPlayerManager.cpp b/main/source/mod/AvHAIPlayerManager.cpp index 042b7986..dbb6b750 100644 --- a/main/source/mod/AvHAIPlayerManager.cpp +++ b/main/source/mod/AvHAIPlayerManager.cpp @@ -8,6 +8,7 @@ #include "AvHAIHelper.h" #include "AvHAICommander.h" #include "AvHAIPlayerUtil.h" +#include "AvHAISoundQueue.h" #include "AvHGamerules.h" #include "../dlls/client.h" #include @@ -51,6 +52,8 @@ float CountdownStartedTime = 0.0f; bool bBotsEnabled = false; +float CurrentFrameDelta = 0.01f; + AvHAICommanderMode AIMGR_GetCommanderMode() { if (avh_botcommandermode.value == 1) @@ -560,6 +563,8 @@ void AIMGR_UpdateAIPlayers() float FrameDelta = CurrTime - PrevTime; + AIMGR_SetFrameDelta(FrameDelta); + int cvarBotSkill = clampi((int)avh_botskill.value, 0, 3); bool bSkillChanged = (cvarBotSkill != CurrentBotSkill); @@ -592,6 +597,8 @@ void AIMGR_UpdateAIPlayers() AIMGR_SetCommanderAllowedTime(TeamBNumber, gpGlobals->time + 15.0f); } } + + AIMGR_ProcessPendingSounds(); } int NumCommanders = AIMGR_GetNumAICommanders(); @@ -709,7 +716,7 @@ AvHTeamNumber AIMGR_GetTeamANumber() AvHTeamNumber AIMGR_GetTeamBNumber() { - return GetGameRules()->GetTeamANumber(); + return GetGameRules()->GetTeamBNumber(); } AvHTeam* AIMGR_GetTeamRef(const AvHTeamNumber Team) @@ -1450,4 +1457,100 @@ bool AIMGR_IsMatchPracticallyOver() } return false; +} + +void AIMGR_ProcessPendingSounds() +{ + float FrameDelta = AIMGR_GetFrameDelta(); + + for (auto it = ActiveAIPlayers.begin(); it != ActiveAIPlayers.end(); it++) + { + it->HearingThreshold -= FrameDelta; + it->HearingThreshold = clampf(it->HearingThreshold, 0.0f, 1.0f); + } + + AvHAISound Sound = AISND_PopSound(); + + AvHTeamNumber TeamANumber = AIMGR_GetTeamANumber(); + AvHTeamNumber TeamBNumber = AIMGR_GetTeamBNumber(); + + while (Sound.SoundType != AI_SOUND_NONE) + { + edict_t* EmittingEntity = INDEXENT(Sound.EntIndex); + //string SoundType = "Unknown"; + + float MaxDist = 0.0f; + + switch (Sound.SoundType) + { + case AI_SOUND_FOOTSTEP: + MaxDist = UTIL_MetresToGoldSrcUnits(20.0f); + //SoundType = "Footstep"; + break; + case AI_SOUND_SHOOT: + MaxDist = UTIL_MetresToGoldSrcUnits(30.0f); + //SoundType = "Shoot"; + break; + case AI_SOUND_VOICELINE: + MaxDist = UTIL_MetresToGoldSrcUnits(20.0f); + //SoundType = "Voiceline"; + break; + case AI_SOUND_LANDING: + MaxDist = UTIL_MetresToGoldSrcUnits(20.0f); + //SoundType = "Landing"; + break; + case AI_SOUND_OTHER: + default: + MaxDist = UTIL_MetresToGoldSrcUnits(20.0f); + //SoundType = "Other"; + break; + } + + MaxDist = sqrf(MaxDist); + + if (!FNullEnt(EmittingEntity) && EmittingEntity->v.team != 0 && IsEdictPlayer(EmittingEntity) && IsPlayerActiveInGame(EmittingEntity)) + { + AvHTeamNumber EmitterTeam = (AvHTeamNumber)EmittingEntity->v.team; + + for (auto it = ActiveAIPlayers.begin(); it != ActiveAIPlayers.end(); it++) + { + AvHTeamNumber ThisTeam = it->Player->GetTeam(); + float Volume = Sound.Volume; + float HearingThresholdScalar = (ThisTeam != EmitterTeam || EmittingEntity == it->Edict) ? 1.0f : 0.5f; + + if (EmitterTeam != ThisTeam) + { + float DistFromSound = vDist3DSq(Sound.SoundLocation, it->Edict->v.origin); + + if (DistFromSound > MaxDist) { continue; } + + Volume = Sound.Volume - (Sound.Volume * clampf((DistFromSound / MaxDist), 0.0f, 1.0f)); + } + + Volume = Volume * HearingThresholdScalar; + + if (Volume > it->HearingThreshold) + { + it->HearingThreshold = Volume; + + if (EmitterTeam != ThisTeam) + { + AIPlayerHearEnemy(&(*it), EmittingEntity, Volume); + } + } + } + } + + Sound = AISND_PopSound(); + } +} + +void AIMGR_SetFrameDelta(float NewValue) +{ + CurrentFrameDelta = NewValue; +} + +float AIMGR_GetFrameDelta() +{ + return CurrentFrameDelta; } \ No newline at end of file diff --git a/main/source/mod/AvHAIPlayerManager.h b/main/source/mod/AvHAIPlayerManager.h index 0be70044..9b50d6af 100644 --- a/main/source/mod/AvHAIPlayerManager.h +++ b/main/source/mod/AvHAIPlayerManager.h @@ -126,4 +126,9 @@ bool AIMGR_HasMatchEnded(); bool AIMGR_IsMatchPracticallyOver(); +void AIMGR_ProcessPendingSounds(); + +void AIMGR_SetFrameDelta(float NewValue); +float AIMGR_GetFrameDelta(); + #endif \ No newline at end of file diff --git a/main/source/mod/AvHAISoundQueue.cpp b/main/source/mod/AvHAISoundQueue.cpp new file mode 100644 index 00000000..3c0163a3 --- /dev/null +++ b/main/source/mod/AvHAISoundQueue.cpp @@ -0,0 +1,35 @@ +#include "AvHAISoundQueue.h" +#include "AvHAIPlayerManager.h" + +std::vector PendingSounds; + +void AISND_RegisterNewSound(int EntIndex, float* NewLocation, AvHAISoundType NewSoundType, float Volume) +{ + if (!AIMGR_IsBotEnabled() || Volume < 0.01f) { return; } + + AvHAISound NewSound; + NewSound.EntIndex = EntIndex; + NewSound.SoundLocation[0] = NewLocation[0]; + NewSound.SoundLocation[1] = NewLocation[1]; + NewSound.SoundLocation[2] = NewLocation[2]; + NewSound.Volume = Volume; + NewSound.SoundType = NewSoundType; + + PendingSounds.push_back(NewSound); +} + +AvHAISound AISND_PopSound() +{ + if (PendingSounds.size() == 0) { return AvHAISound(); } + + AvHAISound Result = PendingSounds.back(); + + PendingSounds.pop_back(); + + return Result; +} + +void AISND_ClearSounds() +{ + PendingSounds.clear(); +} \ No newline at end of file diff --git a/main/source/mod/AvHAISoundQueue.h b/main/source/mod/AvHAISoundQueue.h new file mode 100644 index 00000000..d320c380 --- /dev/null +++ b/main/source/mod/AvHAISoundQueue.h @@ -0,0 +1,35 @@ +#ifndef AVH_AI_SOUND_QUEUE +#define AVH_AI_SOUND_QUEUE + + +#include + +// Sound types affect how audible they are to bots, how easy it is to pinpoint the location of the sound etc +typedef enum _AVHAISOUNDTYPE +{ + AI_SOUND_NONE = 0, // Blank sound + AI_SOUND_FOOTSTEP, // Footstep sound + AI_SOUND_LANDING, // Landing sound, THUD + AI_SOUND_SHOOT, // Pew pew + AI_SOUND_VOICELINE, // Player played a voice line (e.g. "Need a medpack") + AI_SOUND_OTHER // Miscellaneous sound e.g. building +} AvHAISoundType; + +typedef struct _AVHAISOUND +{ + int EntIndex = 0; + float SoundLocation[3] = {0.0f, 0.0f, 0.0f}; + float Volume = 1.0f; + AvHAISoundType SoundType = AI_SOUND_NONE; + +} AvHAISound; + + +void AISND_RegisterNewSound(int EntIndex, float* NewLocation, AvHAISoundType NewSoundType, float Volume = 1.0f); + +AvHAISound AISND_PopSound(); + +void AISND_ClearSounds(); + + +#endif \ No newline at end of file diff --git a/main/source/mod/AvHAITask.cpp b/main/source/mod/AvHAITask.cpp index 0d17f17d..c0c4dd39 100644 --- a/main/source/mod/AvHAITask.cpp +++ b/main/source/mod/AvHAITask.cpp @@ -1931,7 +1931,7 @@ void BotProgressEvolveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { if ((gpGlobals->time - Task->TaskStartedTime) > 1.0f) { - Task->TaskLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), pBot->Edict->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + Task->TaskLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), pBot->Edict->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); if (vIsZero(Task->TaskLocation)) { diff --git a/main/source/mod/AvHAIWeaponHelper.cpp b/main/source/mod/AvHAIWeaponHelper.cpp index c4478927..3abc0342 100644 --- a/main/source/mod/AvHAIWeaponHelper.cpp +++ b/main/source/mod/AvHAIWeaponHelper.cpp @@ -562,128 +562,12 @@ AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target) { return GetBotMarineSecondaryWeapon(pBot); } - else - { - return UTIL_GetPlayerPrimaryWeapon(pBot->Player); - } + return UTIL_GetPlayerPrimaryWeapon(pBot->Player); } if (IsEdictPlayer(target)) { - float DistFromEnemy = vDist2DSq(pBot->Edict->v.origin, target->v.origin); - - if (UTIL_GetPlayerPrimaryWeapon(pBot->Player) == WEAPON_MARINE_GL) - { - if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 && DistFromEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) - { - return WEAPON_MARINE_GL; - } - - if (BotGetSecondaryWeaponClipAmmo(pBot) > 0) - { - return GetBotMarineSecondaryWeapon(pBot); - } - - return WEAPON_MARINE_KNIFE; - } - - if (DistFromEnemy <= sqrf(UTIL_MetresToGoldSrcUnits(2.0f))) - { - if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) == 0) - { - if (BotGetSecondaryWeaponClipAmmo(pBot) > 0) - { - return GetBotMarineSecondaryWeapon(pBot); - } - else - { - return WEAPON_MARINE_KNIFE; - } - } - else - { - return UTIL_GetPlayerPrimaryWeapon(pBot->Player); - } - } - else - { - AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); - - if (PrimaryWeapon == WEAPON_MARINE_SHOTGUN) - { - if (DistFromEnemy > sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) - { - if (BotGetSecondaryWeaponClipAmmo(pBot) > 0 || BotGetSecondaryWeaponAmmoReserve(pBot) > 0) - { - return GetBotMarineSecondaryWeapon(pBot); - } - else - { - if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) - { - return PrimaryWeapon; - } - else - { - return WEAPON_MARINE_KNIFE; - } - } - } - else - { - if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) - { - return PrimaryWeapon; - } - else - { - if (BotGetSecondaryWeaponClipAmmo(pBot) > 0) - { - return GetBotMarineSecondaryWeapon(pBot); - } - else - { - return WEAPON_MARINE_KNIFE; - } - } - } - } - else - { - if (DistFromEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) - { - if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) - { - return PrimaryWeapon; - } - - if (BotGetSecondaryWeaponClipAmmo(pBot) > 0 || BotGetSecondaryWeaponAmmoReserve(pBot) > 0) - { - return GetBotMarineSecondaryWeapon(pBot); - } - - return WEAPON_MARINE_KNIFE; - } - else - { - if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || (DistFromEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f)) && UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0)) - { - return PrimaryWeapon; - } - else - { - if (BotGetSecondaryWeaponClipAmmo(pBot) > 0) - { - return GetBotMarineSecondaryWeapon(pBot); - } - else - { - return WEAPON_MARINE_KNIFE; - } - } - } - } - } + return MarineGetBestWeaponForPlayerTarget(pBot, dynamic_cast(CBaseEntity::Instance(target))); } else { @@ -691,6 +575,14 @@ AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target) } } +bool BotAnyWeaponNeedsReloading(AvHAIPlayer* pBot) +{ + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) < UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player) && UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) { return true; } + if (UTIL_GetPlayerSecondaryWeaponClipAmmo(pBot->Player) < UTIL_GetPlayerSecondaryWeaponMaxClipSize(pBot->Player) && UTIL_GetPlayerSecondaryAmmoReserve(pBot->Player) > 0) { return true; } + + return false; +} + AvHAIWeapon BotAlienChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* target) { AvHAIDeployableStructureType StructureType = GetStructureTypeFromEdict(target); @@ -772,6 +664,114 @@ AvHAIWeapon BotMarineChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* ta return WEAPON_MARINE_KNIFE; } +AvHAIWeapon MarineGetBestWeaponForPlayerTarget(AvHAIPlayer* pBot, AvHPlayer* Target) +{ + AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); + AvHAIWeapon SecondaryWeapon = UTIL_GetPlayerSecondaryWeapon(pBot->Player); + AvHAIWeapon CurrentWeapon = GetPlayerCurrentWeapon(pBot->Player); + + float DistToEnemy = vDist2DSq(pBot->Edict->v.origin, Target->pev->origin); + + bool bHasAmmoForPrimary = (PrimaryWeapon != WEAPON_INVALID && UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0); + bool bHasAmmoForSecondary = (SecondaryWeapon != WEAPON_INVALID && UTIL_GetPlayerSecondaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerSecondaryAmmoReserve(pBot->Player) > 0); + + if (PrimaryWeapon != WEAPON_INVALID && UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0) + { + if (PrimaryWeapon == WEAPON_MARINE_GL) + { + if (DistToEnemy > sqrf(BALANCE_VAR(kGrenadeRadius)) || !bHasAmmoForSecondary) + { + return PrimaryWeapon; + } + else + { + if (bHasAmmoForSecondary) + { + return SecondaryWeapon; + } + else + { + return WEAPON_MARINE_KNIFE; + } + } + } + else if (PrimaryWeapon == WEAPON_MARINE_SHOTGUN) + { + float MaxDist = (IsPlayerMarine(Target) || Target->GetUser3() > AVH_USER3_ALIEN_PLAYER3) ? UTIL_MetresToGoldSrcUnits(15.0f) : UTIL_MetresToGoldSrcUnits(8.0f); + + // Give a little extra leeway if the bot is currently holding a shotgun. Helps prevent rapid switching if the enemy is right on the edge of the max distance + if (CurrentWeapon == PrimaryWeapon) + { + MaxDist *= 1.25f; + } + + if (DistToEnemy < sqrf(MaxDist) || !bHasAmmoForSecondary) + { + return PrimaryWeapon; + } + else + { + if (bHasAmmoForSecondary) + { + return SecondaryWeapon; + } + else + { + return WEAPON_MARINE_KNIFE; + } + } + } + else + { + return PrimaryWeapon; + } + } + + bool bEnemyIsRanged = IsPlayerMarine(Target) || ((GetPlayerCurrentWeapon(Target) == WEAPON_FADE_ACIDROCKET || GetPlayerCurrentWeapon(Target) == WEAPON_LERK_SPORES) && DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))); + + if (bEnemyIsRanged) + { + if (bHasAmmoForSecondary) + { + return SecondaryWeapon; + } + else + { + return WEAPON_MARINE_KNIFE; + } + } + + if (DistToEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) + { + if (bHasAmmoForPrimary) + { + return PrimaryWeapon; + } + else if (bHasAmmoForSecondary) + { + return SecondaryWeapon; + } + else + { + return WEAPON_MARINE_KNIFE; + } + } + + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0) + { + return PrimaryWeapon; + } + else if (UTIL_GetPlayerSecondaryWeaponClipAmmo(pBot->Player) > 0) + { + return SecondaryWeapon; + } + else + { + return WEAPON_MARINE_KNIFE; + } + +} + AvHAIWeapon GorgeGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target) { // Apparently I only imagined bile bomb doing damage to marine armour. Leaving it commented out in case we want to enable it again in future diff --git a/main/source/mod/AvHAIWeaponHelper.h b/main/source/mod/AvHAIWeaponHelper.h index 589157d3..21034664 100644 --- a/main/source/mod/AvHAIWeaponHelper.h +++ b/main/source/mod/AvHAIWeaponHelper.h @@ -43,8 +43,11 @@ bool IsMeleeWeapon(const AvHAIWeapon Weapon); Vector UTIL_GetGrenadeThrowTarget(edict_t* Player, const Vector TargetLocation, const float ExplosionRadius, bool bPrecise); AvHAIWeapon BotMarineChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* target); +AvHAIWeapon MarineGetBestWeaponForPlayerTarget(AvHAIPlayer* pBot, AvHPlayer* Target); AvHAIWeapon BotAlienChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* target); +bool BotAnyWeaponNeedsReloading(AvHAIPlayer* pBot); + // Helper function to pick the best weapon for any given situation and target type. AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target); AvHAIWeapon BotAlienChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target); diff --git a/main/source/mod/AvHBasePlayerWeapon.cpp b/main/source/mod/AvHBasePlayerWeapon.cpp index 10e55bbd..1bf1f654 100644 --- a/main/source/mod/AvHBasePlayerWeapon.cpp +++ b/main/source/mod/AvHBasePlayerWeapon.cpp @@ -86,7 +86,7 @@ extern int g_runfuncs; #ifdef AVH_SERVER #include "AvHServerUtil.h" #include "AvHGamerules.h" - +#include "AvHAISoundQueue.h" extern int gWelderConstEventID; #endif @@ -1048,11 +1048,32 @@ void AvHBasePlayerWeapon::PrimaryAttack(void) this->PlaybackEvent(this->mEvent, this->GetShootAnimation()); this->SetAnimationAndSound(); + + // If player is too close to a wall, don't actually fire the projectile if(this->GetIsGunPositionValid()) { this->FireProjectiles(); +#ifdef AVH_SERVER + + float SoundVolume = 1.0f; + + int theSilenceLevel = AvHGetAlienUpgradeLevel(this->m_pPlayer->pev->iuser4, MASK_UPGRADE_6); + switch (theSilenceLevel) + { + case 1: + SoundVolume = (float)BALANCE_VAR(kSilenceLevel1Volume); + break; + case 2: + SoundVolume = (float)BALANCE_VAR(kSilenceLevel2Volume); + break; + case 3: + SoundVolume = (float)BALANCE_VAR(kSilenceLevel3Volume); + break; + } + AISND_RegisterNewSound(this->m_pPlayer->entindex(), this->m_pPlayer->pev->origin, AI_SOUND_SHOOT, SoundVolume); +#endif } else { diff --git a/main/source/mod/AvHBite.cpp b/main/source/mod/AvHBite.cpp index 7c0cc51b..94ed7e40 100644 --- a/main/source/mod/AvHBite.cpp +++ b/main/source/mod/AvHBite.cpp @@ -64,6 +64,7 @@ #ifdef AVH_SERVER #include "AvHGamerules.h" #include "AvHServerUtil.h" +#include "AvHAISoundQueue.h" #endif #include "AvHSharedUtil.h" @@ -221,6 +222,8 @@ void AvHBite::FireProjectiles(void) { theSoundToPlay = kBiteKillSound; } + + AISND_RegisterNewSound(this->m_pPlayer->entindex(), this->m_pPlayer->pev->origin, AI_SOUND_LANDING, 1.0f); EMIT_SOUND(ENT(pev), CHAN_WEAPON, theSoundToPlay, 1.0, ATTN_NORM); } diff --git a/main/source/mod/AvHClaws.cpp b/main/source/mod/AvHClaws.cpp index fc401f0b..1faf4986 100644 --- a/main/source/mod/AvHClaws.cpp +++ b/main/source/mod/AvHClaws.cpp @@ -70,6 +70,7 @@ #ifdef AVH_SERVER #include "AvHGamerules.h" #include "AvHServerUtil.h" +#include "AvHAISoundQueue.h" #endif #include "AvHSharedUtil.h" @@ -235,6 +236,8 @@ void AvHClaws::FireProjectiles(void) float theForceScalar = theDamage*.2f; CBaseEntity* theAttacker = this->m_pPlayer; AvHSUExplosiveForce(pHurt->pev->origin, 100, theForceScalar, theAttacker, theAttacker); + + AISND_RegisterNewSound(pHurt->entindex(), this->m_pPlayer->pev->origin, AI_SOUND_LANDING, 1.0f); // Played in event now //EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, theSoundToPlay, 1.0, ATTN_NORM, 0, 100 + theAdrenalineFactor*30 + RANDOM_LONG(-3,3) ); diff --git a/main/source/mod/AvHMarineEquipment.cpp b/main/source/mod/AvHMarineEquipment.cpp index bbb89a2e..763cc839 100644 --- a/main/source/mod/AvHMarineEquipment.cpp +++ b/main/source/mod/AvHMarineEquipment.cpp @@ -146,7 +146,10 @@ #include "AvHSiegeTurret.h" #include "AvHHulls.h" +#ifdef AVH_SERVER #include "AvHAIPlayerManager.h" +#include "AvHAISoundQueue.h" +#endif //LINK_ENTITY_TO_CLASS(kwMine, AvHMine); //LINK_ENTITY_TO_CLASS(kwDeployedTurret, AvHDeployedTurret); @@ -1286,6 +1289,10 @@ void AvHPhaseGate::TeleportUse(CBaseEntity *pActivator, CBaseEntity *pCaller, US this->SetTimeOfLastDeparture(gpGlobals->time); AvHSUPlayPhaseInEffect(theFlags, this, thePlayer); +#ifdef AVH_SERVER + AISND_RegisterNewSound(thePlayer->entindex(), theOrigin, AI_SOUND_OTHER, 1.0f); +#endif + // AvHSUKillPlayersTouchingPlayer(thePlayer, this->pev); AvHSUPushbackPlayersTouchingPlayer(thePlayer, this->pev); KillBuildablesTouchingPlayer(thePlayer, this->pev); @@ -1978,11 +1985,13 @@ void AvHCommandStation::CommandUse( CBaseEntity* pActivator, CBaseEntity* pCalle GetGameRules()->MarkDramaticEvent(kCCNewCommanderPriority, thePlayer, this); +#ifdef AVH_SERVER // A human used the comm chair, let the AI know to keep away if (!(thePlayer->pev->flags & FL_FAKECLIENT)) { AIMGR_SetCommanderAllowedTime(theStationTeamNumber, gpGlobals->time + 20.0f); } +#endif } else { diff --git a/main/source/mod/AvHPlayer.cpp b/main/source/mod/AvHPlayer.cpp index d8d58346..785f8f4e 100644 --- a/main/source/mod/AvHPlayer.cpp +++ b/main/source/mod/AvHPlayer.cpp @@ -255,7 +255,10 @@ #include "AvHNetworkMessages.h" #include "AvHNexusServer.h" +#ifdef AVH_SERVER #include "AvHAIPlayerManager.h" +#include "AvHAISoundQueue.h" +#endif std::string GetLogStringForPlayer( edict_t *pEntity ); @@ -2309,6 +2312,26 @@ void AvHPlayer::StartLeap() // Make sure player has leap if(this->pev->iuser3 == AVH_USER3_ALIEN_PLAYER1) { +#ifdef AVH_SERVER + + float SoundVolume = 1.0f; + + int theSilenceLevel = AvHGetAlienUpgradeLevel(this->pev->iuser4, MASK_UPGRADE_6); + switch (theSilenceLevel) + { + case 1: + SoundVolume = (float)BALANCE_VAR(kSilenceLevel1Volume); + break; + case 2: + SoundVolume = (float)BALANCE_VAR(kSilenceLevel2Volume); + break; + case 3: + SoundVolume = (float)BALANCE_VAR(kSilenceLevel3Volume); + break; + } + + AISND_RegisterNewSound(this->entindex(), this->pev->origin, AI_SOUND_SHOOT, SoundVolume); +#endif this->mTimeLeapEnd = gpGlobals->time + kLeapDuration; } } @@ -4973,6 +4996,11 @@ bool AvHPlayer::PlaySaying(AvHMessageID inMessageID) //int pitch = 95;// + RANDOM_LONG(0,29); //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, theSaying, 1, ATTN_NORM, 0, pitch); gSoundListManager.PlaySoundInList(theSoundList, this, CHAN_VOICE, 1.0f); + +#ifdef AVH_SERVER + AISND_RegisterNewSound(this->entindex(), this->pev->origin, AI_SOUND_VOICELINE, 1.0f); +#endif + thePlayedSaying = true; } @@ -7034,6 +7062,28 @@ void AvHPlayer::InternalMovementThink() } } else { + +#ifdef AVH_SERVER + + float SoundVolume = 1.0f; + + int theSilenceLevel = AvHGetAlienUpgradeLevel(this->pev->iuser4, MASK_UPGRADE_6); + switch (theSilenceLevel) + { + case 1: + SoundVolume = (float)BALANCE_VAR(kSilenceLevel1Volume); + break; + case 2: + SoundVolume = (float)BALANCE_VAR(kSilenceLevel2Volume); + break; + case 3: + SoundVolume = (float)BALANCE_VAR(kSilenceLevel3Volume); + break; + } + + AISND_RegisterNewSound(this->entindex(), this->pev->origin, AI_SOUND_SHOOT, SoundVolume); +#endif + EMIT_SOUND_DYN(ENT(this->pev), CHAN_WEAPON, theSoundToPlay, theVolumeScalar, ATTN_NORM, 0, 100); this->mTimeOfLastMovementSound = gpGlobals->time; } @@ -8753,6 +8803,10 @@ bool AvHPlayer::Heal(float inAmount, bool inPlaySound, bool dcHealing) { // Play regeneration event PLAYBACK_EVENT_FULL(0, this->edict(), gRegenerationEventID, 0, this->pev->origin, (float *)&g_vecZero, this->GetAlienAdjustedEventVolume(), 0.0, /*theWeaponIndex*/ 0, 0, 0, 0 ); +#ifdef AVH_SERVER + // Heartbeat sound is a little loud, so reduce it for the AI so bots don't instantly spot your location with a single regen tic + AISND_RegisterNewSound(this->entindex(), this->pev->origin, AI_SOUND_OTHER, this->GetAlienAdjustedEventVolume() * 0.25f); +#endif } } } @@ -9177,7 +9231,8 @@ int AvHPlayer::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, floa { CBasePlayer* inAttackingPlayer = dynamic_cast(CBaseEntity::Instance(ENT(pevAttacker))); - if (inAttackingPlayer) +#ifdef AVH_SERVER + if (inAttackingPlayer && AIMGR_IsBotEnabled()) { AvHAIPlayer* VictimBot = AIMGR_GetBotRefFromPlayer(this); @@ -9186,7 +9241,7 @@ int AvHPlayer::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, floa AIPlayerTakeDamage(VictimBot, flDamage, inAttackingPlayer->edict()); } } - +#endif const char* inWeaponName = STRING(pevInflictor->classname); if(inAttackingPlayer && inWeaponName) { diff --git a/main/source/mod/AvHServerUtil.cpp b/main/source/mod/AvHServerUtil.cpp index 773f874e..7c76d72f 100644 --- a/main/source/mod/AvHServerUtil.cpp +++ b/main/source/mod/AvHServerUtil.cpp @@ -123,6 +123,8 @@ #include "AvHHulls.h" #include "AnimationUtil.h" +#include "AvHAISoundQueue.h" + int NS_PointContents(const hull_t *hull, int num, float p[3]); float NS_TraceLineAgainstEntity(int inEntityIndex, float inTime, const float inRayOrigin[3], const float inRayDirection[3]); @@ -573,6 +575,8 @@ void AvHSUPlayRandomConstructionEffect(AvHPlayer* inPlayer, CBaseEntity* inConst { gSoundListManager.PlaySoundInList(kMarineConstructionSoundList, inConstructee, CHAN_BODY, theVolume); + AISND_RegisterNewSound(inPlayer->entindex(), inPlayer->pev->origin, AI_SOUND_OTHER, theVolume); + // Play sparks every other time if(RANDOM_LONG(0, 1) == 1) { diff --git a/main/source/mod/AvHSwipe.cpp b/main/source/mod/AvHSwipe.cpp index 9b0f6d1c..0b6090c1 100644 --- a/main/source/mod/AvHSwipe.cpp +++ b/main/source/mod/AvHSwipe.cpp @@ -67,6 +67,7 @@ #ifdef AVH_SERVER #include "AvHGamerules.h" #include "AvHServerUtil.h" +#include "AvHAISoundQueue.h" #endif #include "AvHSharedUtil.h" @@ -254,6 +255,8 @@ void AvHSwipe::FireProjectiles(void) ASSERT(theSoundToPlay); EMIT_SOUND(ENT(pev), CHAN_WEAPON, theSoundToPlay, 1.0, ATTN_NORM); + + AISND_RegisterNewSound(pHurt->entindex(), this->m_pPlayer->pev->origin, AI_SOUND_LANDING, 1.0f); } } diff --git a/main/source/pm_shared/pm_shared.cpp b/main/source/pm_shared/pm_shared.cpp index 980c8356..795a8bad 100644 --- a/main/source/pm_shared/pm_shared.cpp +++ b/main/source/pm_shared/pm_shared.cpp @@ -107,6 +107,9 @@ #include "../mod/CollisionUtil.h" #include "../engine/studio.h" +#ifdef AVH_SERVER +#include "../mod/AvHAISoundQueue.h" +#endif //#ifdef AVH_SERVER #include "../engine/edict.h" @@ -2008,6 +2011,9 @@ void NS_PlayStepSound(int inMaterialType, int inSoundNumber, float inVolume) // Play it at the specified volume PM_NSPlaySound(CHAN_BODY, theFinalName, inVolume, theNorm, 0, PITCH_NORM); +#ifdef AVH_SERVER + AISND_RegisterNewSound(pmove->player_index + 1, pmove->origin, AI_SOUND_FOOTSTEP, inVolume); +#endif } else { @@ -4613,6 +4619,10 @@ bool PM_FlapMove() theVolumeScalar = min(max(theVolumeScalar, 0.0f), 1.0f); PM_NSPlaySound(CHAN_BODY, theSoundToPlay, theVolumeScalar, ATTN_NORM, 0, PITCH_NORM); + +#ifdef AVH_SERVER + AISND_RegisterNewSound(pmove->player_index + 1, pmove->origin, AI_SOUND_FOOTSTEP, theVolumeScalar); +#endif } pmove->oldbuttons |= IN_JUMP; // don't jump again until released @@ -5700,6 +5710,9 @@ void PM_CheckFalling( void ) //break; //case 1: PM_NSPlaySound( CHAN_VOICE, theFallPainSound, theFallPainVolume, ATTN_NORM, 0, PITCH_NORM ); +#ifdef AVH_SERVER + AISND_RegisterNewSound(pmove->player_index + 1, pmove->origin, AI_SOUND_LANDING, theFallPainVolume); +#endif // break; //} fvol = 1.0;