From a0700fcd4fe3068a609bccfd6afc43c813f4013c Mon Sep 17 00:00:00 2001 From: RGreenlees Date: Fri, 26 Jan 2024 23:06:31 +0000 Subject: [PATCH] Fix grenade throwing, better combat --- main/source/mod/AIPlayers/AvHAICommander.cpp | 43 +- main/source/mod/AIPlayers/AvHAINavigation.cpp | 13 +- main/source/mod/AIPlayers/AvHAIPlayer.cpp | 408 +++++++++++++++++- main/source/mod/AIPlayers/AvHAIPlayer.h | 3 + .../mod/AIPlayers/AvHAIPlayerManager.cpp | 2 +- main/source/mod/AIPlayers/AvHAIPlayerUtil.cpp | 7 + main/source/mod/AIPlayers/AvHAITactical.cpp | 27 ++ main/source/mod/AIPlayers/AvHAITactical.h | 2 + main/source/mod/AIPlayers/AvHAITask.cpp | 36 +- main/source/mod/AvHMessage.h | 2 +- 10 files changed, 503 insertions(+), 40 deletions(-) diff --git a/main/source/mod/AIPlayers/AvHAICommander.cpp b/main/source/mod/AIPlayers/AvHAICommander.cpp index 56e7b241..d97d2f5f 100644 --- a/main/source/mod/AIPlayers/AvHAICommander.cpp +++ b/main/source/mod/AIPlayers/AvHAICommander.cpp @@ -791,10 +791,8 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot) if (NumInfantryPortals < 2) { - if (AICOMM_BuildInfantryPortal(pBot, CommChair)) - { - return true; - } + AICOMM_BuildInfantryPortal(pBot, CommChair); + return true; } StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY; @@ -823,10 +821,8 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot) if (!vIsZero(BuildLocation)) { - if (AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_ARMOURY, BuildLocation)) - { - return true; - } + AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_ARMOURY, BuildLocation); + return true; } } @@ -858,8 +854,9 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot) { Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair->v.origin, UTIL_MetresToGoldSrcUnits(10.0f)); - if (AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation)) + if (!vIsZero(BuildLocation)) { + AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation); return true; } } @@ -869,10 +866,8 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot) if (CappableNode) { - if (AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_RESTOWER, CappableNode->Location)) - { - return true; - } + AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_RESTOWER, CappableNode->Location); + return true; } const AvHAIHiveDefinition* HiveToSecure = AICOMM_GetEmptyHiveOpportunityNearestLocation(pBot, AITAC_GetCommChairLocation(TeamNumber)); @@ -1973,6 +1968,7 @@ bool AICOMM_ShouldCommanderLeaveChair(AvHAIPlayer* pBot) const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPlayer* CommanderBot, const Vector SearchLocation) { AvHTeamNumber CommanderTeam = CommanderBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(CommanderTeam); const AvHAIHiveDefinition* Result = nullptr; float MinDist = 0.0f; @@ -2009,6 +2005,27 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl if (AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, SecureLocation, MarineDist) == nullptr) { continue; } + int NumEnemiesNearby = AITAC_GetNumPlayersOnTeamWithLOS(EnemyTeam, SecureLocation + Vector(0.0f, 0.0f, 10.0f), UTIL_MetresToGoldSrcUnits(15.0f), nullptr); + + if (NumEnemiesNearby > 0) { continue; } + + DeployableSearchFilter EnemyStuff; + EnemyStuff.DeployableTeam = EnemyTeam; + EnemyStuff.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + EnemyStuff.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); + + if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE) + { + EnemyStuff.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); + } + else + { + EnemyStuff.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER; + } + + if (AITAC_DeployableExistsAtLocation(SecureLocation, &EnemyStuff)) { continue; } + float ThisDist = vDist2DSq(Hive->FloorLocation, SearchLocation); if (!Result || ThisDist < MinDist) diff --git a/main/source/mod/AIPlayers/AvHAINavigation.cpp b/main/source/mod/AIPlayers/AvHAINavigation.cpp index f1d74f87..3cfd2eaa 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.cpp +++ b/main/source/mod/AIPlayers/AvHAINavigation.cpp @@ -4933,7 +4933,7 @@ void UTIL_UpdateBotMovementStatus(AvHAIPlayer* pBot) bool AbortCurrentMove(AvHAIPlayer* pBot, const Vector NewDestination) { - if (pBot->BotNavInfo.CurrentPath.size() == 0) { return true; } + if (pBot->BotNavInfo.CurrentPath.size() == 0 || pBot->BotNavInfo.NavProfile.bFlyingProfile) { return true; } if (IsBotPermaStuck(pBot)) { @@ -5764,8 +5764,6 @@ void SkipAheadInFlightPath(AvHAIPlayer* pBot) // Early exit if we don't have a path, or we're already on the last path point if (BotNavInfo->CurrentPath.size() == 0 || BotNavInfo->CurrentPathPoint == prev(BotNavInfo->CurrentPath.end())) { return; } - - if (UTIL_QuickHullTrace(pBot->Edict, pBot->Edict->v.origin, prev(BotNavInfo->CurrentPath.end())->Location, head_hull)) { pBot->BotNavInfo.CurrentPathPoint = prev(BotNavInfo->CurrentPath.end()); @@ -5897,7 +5895,14 @@ void BotFollowFlightPath(AvHAIPlayer* pBot) } } - BotMoveLookAt(pBot, CurrentMoveDest); + Vector LookLocation = CurrentMoveDest; + + if (pEdict->v.origin.z < BotNavInfo->CurrentPathPoint->requiredZ) + { + LookLocation.z += 32.0f; + } + + BotMoveLookAt(pBot, LookLocation); pBot->desiredMovementDir = UTIL_GetForwardVector2D(pEdict->v.v_angle); diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.cpp b/main/source/mod/AIPlayers/AvHAIPlayer.cpp index 93fbae90..d98e499e 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayer.cpp @@ -978,10 +978,22 @@ void BotShootLocation(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, const Vector { AvHBasePlayerWeapon* WeaponRef = dynamic_cast(pBot->Player->m_pActiveItem); - if (!WeaponRef->GetMustPressTriggerForEachShot() || WeaponRef->m_flNextPrimaryAttack <= 0.0f) + if (CurrentWeapon == WEAPON_MARINE_GRENADE) { - pBot->Button |= IN_ATTACK; + if (!WeaponRef->m_flStartThrow && WeaponRef->m_flReleaseThrow == -1) + { + pBot->Button |= IN_ATTACK; + } } + else + { + if (!WeaponRef->GetMustPressTriggerForEachShot() || WeaponRef->m_flNextPrimaryAttack <= 0.0f) + { + pBot->Button |= IN_ATTACK; + } + } + + } } @@ -1041,6 +1053,24 @@ void BotEvolveLifeform(AvHAIPlayer* pBot, Vector DesiredEvolveLocation, AvHMessa } } +void BotEvolveUpgrade(AvHAIPlayer* pBot, Vector DesiredEvolveLocation, AvHMessageID TargetUpgrade) +{ + Vector EvolvePoint = UTIL_ProjectPointToNavmesh(DesiredEvolveLocation, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); + + if (vIsZero(EvolvePoint)) + { + EvolvePoint = DesiredEvolveLocation; + } + + if (vDist2DSq(pBot->Edict->v.origin, EvolvePoint) > sqrf(32.0f)) + { + MoveTo(pBot, EvolvePoint, MOVESTYLE_NORMAL); + return; + } + + pBot->Impulse = TargetUpgrade; +} + void BotUpdateDesiredViewRotation(AvHAIPlayer* pBot) { // We always prioritise MoveLookLocation if it is set so the bot doesn't screw up wall climbing or ladder movement @@ -1634,7 +1664,29 @@ void StartNewBotFrame(AvHAIPlayer* pBot) void CustomThink(AvHAIPlayer* pBot) { - if (IsPlayerMarine(pBot->Player)) { return; } + if (IsPlayerMarine(pBot->Player)) + { + if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GRENADE)) + { + if (!vIsZero(AIDEBUG_GetDebugVector1())) + { + BotThrowGrenadeAtTarget(pBot, AIDEBUG_GetDebugVector1()); + } + } + else + { + pBot->DesiredCombatWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); + } + + AvHAIWeapon DesiredWeapon = (pBot->DesiredMoveWeapon != WEAPON_NONE) ? pBot->DesiredMoveWeapon : pBot->DesiredCombatWeapon; + + if (DesiredWeapon != WEAPON_NONE && GetPlayerCurrentWeapon(pBot->Player) != DesiredWeapon) + { + BotSwitchToWeapon(pBot, DesiredWeapon); + } + + return; + } if (!IsPlayerFade(pBot->Edict)) { @@ -2455,11 +2507,11 @@ bool MarineCombatThink(AvHAIPlayer* pBot) { edict_t* pEdict = pBot->Edict; - if (pBot->CurrentEnemy < 0) { return false; } - edict_t* CurrentEnemy = pBot->TrackedEnemies[pBot->CurrentEnemy].EnemyEdict; enemy_status* TrackedEnemyRef = &pBot->TrackedEnemies[pBot->CurrentEnemy]; + pBot->LastCombatTime = gpGlobals->time; + // ENEMY IS OUT OF SIGHT if (!TrackedEnemyRef->bHasLOS) @@ -2812,13 +2864,15 @@ void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) for (auto it = BuildableStructures.begin(); it != BuildableStructures.end(); it++) { - int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), (*it)->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + float ThisDist = vDist2D((*it)->Location, pBot->Edict->v.origin); + + int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), (*it)->Location, ThisDist - 5.0f, false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); int NumDesiredBuilders = (vDist2DSq((*it)->Location, AITAC_GetCommChairLocation(pBot->Player->GetTeam())) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) ? 1 : 2; if (NumBuilders < NumDesiredBuilders) { - float ThisDist = vDist2DSq((*it)->Location, pBot->Edict->v.origin); + if (!NearestStructure || ThisDist < MinDist) { NearestStructure = (*it); @@ -2829,7 +2883,7 @@ void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (NearestStructure) { - AITASK_SetBuildTask(pBot, Task, NearestStructure->edict, false); + AITASK_SetBuildTask(pBot, Task, NearestStructure->edict, true); return; } @@ -2866,6 +2920,27 @@ void AIPlayerNSAlienThink(AvHAIPlayer* pBot) if (AlienCombatThink(pBot)) { return; } } + if (pBot->LastCombatTime > 5.0f) + { + if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_DEFENCE) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_DEFENCE)) + { + BotEvolveUpgrade(pBot, pBot->Edict->v.origin, AlienGetDesiredUpgrade(pBot, HIVE_TECH_DEFENCE)); + return; + } + + if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_MOVEMENT) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_MOVEMENT)) + { + BotEvolveUpgrade(pBot, pBot->Edict->v.origin, AlienGetDesiredUpgrade(pBot, HIVE_TECH_MOVEMENT)); + return; + } + + if (!PlayerHasAlienUpgradeOfType(pBot->Edict, HIVE_TECH_SENSORY) && AITAC_IsAlienUpgradeAvailableForTeam(pBot->Player->GetTeam(), HIVE_TECH_SENSORY)) + { + BotEvolveUpgrade(pBot, pBot->Edict->v.origin, AlienGetDesiredUpgrade(pBot, HIVE_TECH_SENSORY)); + return; + } + } + if (pBot->CurrentTask && pBot->CurrentTask->TaskType != TASK_NONE) { BotProgressTask(pBot, pBot->CurrentTask); @@ -2877,6 +2952,26 @@ void AIPlayerNSAlienThink(AvHAIPlayer* pBot) } } +AvHMessageID AlienGetDesiredUpgrade(AvHAIPlayer* pBot, HiveTechStatus DesiredTech) +{ + if (DesiredTech == HIVE_TECH_DEFENCE) + { + return ALIEN_EVOLUTION_ONE; + } + + if (DesiredTech == HIVE_TECH_MOVEMENT) + { + return ALIEN_EVOLUTION_SEVEN; + } + + if (DesiredTech == HIVE_TECH_SENSORY) + { + return ALIEN_EVOLUTION_ELEVEN; + } + + return MESSAGE_NULL; +} + void AIPlayerCOThink(AvHAIPlayer* pBot) { @@ -3165,6 +3260,7 @@ void AIPlayerSetAlienBuilderPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task DeployableSearchFilter EnemyStructureFilter; EnemyStructureFilter.DeployableTeam = EnemyTeam; + EnemyStructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; EnemyStructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; EnemyStructureFilter.DeployableTypes = StructureTypes; EnemyStructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); @@ -3343,6 +3439,37 @@ void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { AvHAIResourceNode* ThisNode = (*it); + if (ThisNode->bIsBaseNode) + { + if (FNullEnt(ThisNode->ParentHive)) { continue; } // This node must belong to marine base, don't try to cap it + } + + if (!FNullEnt(ThisNode->ParentHive)) + { + AvHAIHiveDefinition* ParentHiveRef = AITAC_GetHiveFromEdict(ThisNode->ParentHive); + + // Don't try to cap resource nodes in an enemy hive. + if (ParentHiveRef->OwningTeam == EnemyTeam) { continue; } + + DeployableSearchFilter EnemyStructuresFilter; + EnemyStructuresFilter.DeployableTeam = EnemyTeam; + EnemyStructuresFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + EnemyStructuresFilter.ExcludeStatusFlags = (IsPlayerSkulk(pBot->Edict)) ? (STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_ELECTRIFIED) : STRUCTURE_STATUS_RECYCLING; + EnemyStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE) + { + EnemyStructuresFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); + } + else + { + EnemyStructuresFilter.DeployableTypes = (STRUCTURE_ALIEN_OFFENCECHAMBER); + } + + // Enemy has started fortifying the hive we want to build a RT in, don't try to cap it + if (AITAC_DeployableExistsAtLocation(ThisNode->Location, &EnemyStructuresFilter)) { continue; } + } + edict_t* ExistingBuilder = AITAC_GetNearestPlayerOfClassInArea(BotTeam, ThisNode->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2); if (!FNullEnt(ExistingBuilder) && vDist2DSq(ExistingBuilder->v.origin, ThisNode->Location) < vDist2DSq(pBot->Edict->v.origin, ThisNode->Location) && GetPlayerResources(ExistingBuilder) >= (BALANCE_VAR(kResourceTowerCost) * 0.8f)) { continue; } @@ -3396,22 +3523,37 @@ void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { AvHAIResourceNode* ThisNode = (*it); - // Don't attack nodes which are firmly owned by the enemy (i.e. in marine base, or part of an enemy alien team's active hive) if (ThisNode->bIsBaseNode) { - AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); - if (AIMGR_GetEnemyTeamType(BotTeam) == AVH_CLASS_TYPE_MARINE) + if (FNullEnt(ThisNode->ParentHive)) { continue; } // This node must belong to marine base, leave that tower alone + } + + if (!FNullEnt(ThisNode->ParentHive)) + { + AvHAIHiveDefinition* ParentHiveRef = AITAC_GetHiveFromEdict(ThisNode->ParentHive); + + // Don't try to attack RTs inside enemy hives + if (ParentHiveRef->OwningTeam == EnemyTeam) { continue; } + + // Don't attack an empty hive RT if the enemy has fortified the area + DeployableSearchFilter EnemyStructuresFilter; + EnemyStructuresFilter.DeployableTeam = EnemyTeam; + EnemyStructuresFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + EnemyStructuresFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + EnemyStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE) { - // Too close to the marine comm chair, don't touch this one - if (vDist2DSq(ThisNode->Location, AITAC_GetCommChairLocation(EnemyTeam)) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) { continue; } + // Don't attack if there are turrets or a phase gate in the area. + EnemyStructuresFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); } else { - // The enemy alien team has a hive here, don't attack this res node or we'll get smooshed - AvHAIHiveDefinition* ParentHive = AITAC_GetHiveFromEdict(ThisNode->ParentHive); - - if (ParentHive && ParentHive->OwningTeam == EnemyTeam) { continue; } + EnemyStructuresFilter.DeployableTypes = (STRUCTURE_ALIEN_OFFENCECHAMBER); } + + // Enemy has started fortifying the hive we want to build a RT in, leave that tower alone + if (AITAC_DeployableExistsAtLocation(ThisNode->Location, &EnemyStructuresFilter)) { continue; } } float ThisDist = vDist2DSq(ThisNode->Location, AITAC_GetTeamStartingLocation(EnemyTeam)); @@ -3430,6 +3572,7 @@ void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) return; } + // If we have nothing to do as a capper, then revert to assault AIPlayerSetAlienAssaultPrimaryTask(pBot, Task); } @@ -3462,6 +3605,10 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task { EnemyStuffFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY | STRUCTURE_MARINE_COMMCHAIR); EnemyStuffFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + if (IsPlayerSkulk(pBot->Edict) || IsPlayerLerk(pBot->Edict)) + { + EnemyStuffFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RECYCLING; + } } else { @@ -3598,7 +3745,7 @@ void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task return; } } - // We're a skulk + // We're a skulk or lerk else { if (HiveToGuard) @@ -3831,6 +3978,228 @@ void AIPlayerSetAlienHarasserPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Tas void AIPlayerSetSecondaryAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); + + if (IsPlayerGorge(pBot->Edict)) + { + edict_t* TeamMateToHeal = nullptr; + + vector AllNearbyTeammates = AITAC_GetAllPlayersOfTeamInArea(BotTeam, pBot->Edict->v.origin, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2); + + float MinDist = 0.0f; + + for (auto it = AllNearbyTeammates.begin(); it != AllNearbyTeammates.end(); it++) + { + edict_t* ThisPlayer = (*it)->edict(); + + if (!FNullEnt(ThisPlayer) && IsPlayerActiveInGame(ThisPlayer) && GetPlayerOverallHealthPercent(ThisPlayer) < 0.99f) + { + float ThisDist = vDist2DSq(pBot->Edict->v.origin, ThisPlayer->v.origin); + if (FNullEnt(TeamMateToHeal) || ThisDist < MinDist) + { + TeamMateToHeal = ThisPlayer; + } + } + } + + if (!FNullEnt(TeamMateToHeal)) + { + Task->TaskType = TASK_HEAL; + Task->TaskTarget = TeamMateToHeal; + Task->bTaskIsUrgent = true; + return; + } + + DeployableSearchFilter DamagedStructuresFilter; + DamagedStructuresFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + DamagedStructuresFilter.DeployableTeam = BotTeam; + DamagedStructuresFilter.ReachabilityTeam = BotTeam; + DamagedStructuresFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + DamagedStructuresFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + DamagedStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + vector AllNearbyStructures = AITAC_FindAllDeployables(pBot->Edict->v.origin, &DamagedStructuresFilter); + + edict_t* StructureToHeal = nullptr; + + MinDist = 0.0f; + + for (auto it = AllNearbyStructures.begin(); it != AllNearbyStructures.end(); it++) + { + AvHAIBuildableStructure* ThisStructure = (*it); + + if (ThisStructure && ThisStructure->healthPercent < 0.99f) + { + float ThisDist = vDist2DSq(pBot->Edict->v.origin, ThisStructure->Location); + if (FNullEnt(StructureToHeal) || ThisDist < MinDist) + { + StructureToHeal = ThisStructure->edict; + MinDist = ThisDist; + } + } + } + + if (!FNullEnt(StructureToHeal)) + { + Task->TaskType = TASK_HEAL; + Task->TaskTarget = StructureToHeal; + Task->bTaskIsUrgent = true; + return; + } + + AITASK_ClearBotTask(pBot, Task); + + return; + } + + vector AllHives = AITAC_GetAllTeamHives(BotTeam, false); + + AvHAIHiveDefinition* HiveToDefend = nullptr; + float MinDist = 0.0f; + + for (auto it = AllHives.begin(); it != AllHives.end(); it++) + { + AvHAIHiveDefinition* ThisHive = (*it); + + if (ThisHive && ThisHive->bIsUnderAttack) + { + int AttackerStrength = 0; + int DefenderStrength = 0; + + vector AttackingPlayers = AITAC_GetAllPlayersOfTeamInArea(EnemyTeam, ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(15.0f), false, nullptr, AVH_USER3_NONE); + + for (auto AttackerIt = AttackingPlayers.begin(); AttackerIt != AttackingPlayers.end(); AttackerIt++) + { + AvHPlayer* ThisPlayer = (*AttackerIt); + edict_t* ThisPlayerEdict = ThisPlayer->edict(); + + int ThisAttackerStrength = 1; + + if (PlayerHasWeapon(ThisPlayer, WEAPON_MARINE_HMG) || PlayerHasHeavyArmour(ThisPlayerEdict)) + { + ThisAttackerStrength = 2; + } + + if (IsPlayerFade(ThisPlayerEdict)) + { + ThisAttackerStrength = 2; + } + + if (IsPlayerOnos(ThisPlayerEdict)) + { + ThisAttackerStrength = 3; + } + + AttackerStrength += ThisAttackerStrength; + } + + vector DefendingPlayers = AITAC_GetAllPlayersOfTeamInArea(EnemyTeam, ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(15.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2); + + for (auto DefenderIt = DefendingPlayers.begin(); DefenderIt != DefendingPlayers.end(); DefenderIt++) + { + AvHPlayer* ThisPlayer = (*DefenderIt); + edict_t* ThisPlayerEdict = ThisPlayer->edict(); + + int ThisDefenderStrength = 1; + + if (IsPlayerFade(ThisPlayerEdict)) + { + ThisDefenderStrength = 2; + } + + if (IsPlayerOnos(ThisPlayerEdict)) + { + ThisDefenderStrength = 3; + } + + DefenderStrength += ThisDefenderStrength; + } + + vector AllOtherBots = AIMGR_GetAIPlayersOnTeam(BotTeam); + + for (auto BotIt = AllOtherBots.begin(); BotIt != AllOtherBots.end(); BotIt++) + { + AvHAIPlayer* ThisBot = (*BotIt); + + if (ThisBot != pBot && IsPlayerActiveInGame(ThisBot->Edict) && !IsPlayerGorge(ThisBot->Edict) && vDist2DSq(ThisBot->Edict->v.origin, ThisHive->FloorLocation) > sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) + { + if (ThisBot->SecondaryBotTask.TaskType == TASK_DEFEND && ThisBot->SecondaryBotTask.TaskTarget == ThisHive->HiveEntity->edict()) + { + int ThisDefenderStrength = 1; + + if (IsPlayerFade(ThisBot->Edict)) + { + ThisDefenderStrength = 2; + } + + if (IsPlayerOnos(ThisBot->Edict)) + { + ThisDefenderStrength = 3; + } + + DefenderStrength += ThisDefenderStrength; + } + } + } + + if (AttackerStrength >= DefenderStrength) + { + float ThisDist = vDist2DSq(ThisHive->FloorLocation, pBot->Edict->v.origin); + + if (!HiveToDefend || ThisDist < MinDist) + { + HiveToDefend = ThisHive; + MinDist = ThisDist; + } + } + } + } + + if (HiveToDefend) + { + AITASK_SetDefendTask(pBot, Task, HiveToDefend->HiveEntity->edict(), true); + return; + } + + DeployableSearchFilter AttackedStructuresFilter; + AttackedStructuresFilter.DeployableTypes = (IsPlayerLerk(pBot->Edict)) ? SEARCH_ALL_STRUCTURES : STRUCTURE_ALIEN_RESTOWER; + AttackedStructuresFilter.DeployableTeam = BotTeam; + AttackedStructuresFilter.ReachabilityTeam = BotTeam; + AttackedStructuresFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + AttackedStructuresFilter.IncludeStatusFlags = STRUCTURE_STATUS_UNDERATTACK; + AttackedStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(30.0f); + + vector AllAttackedStructures = AITAC_FindAllDeployables(pBot->Edict->v.origin, &AttackedStructuresFilter); + + AvHAIBuildableStructure* StructureToDefend = nullptr; + MinDist = 0.0f; + + for (auto it = AllAttackedStructures.begin(); it != AllAttackedStructures.end(); it++) + { + AvHAIBuildableStructure* ThisStructure = (*it); + + float ThisDist = vDist2D(pBot->Edict->v.origin, ThisStructure->edict->v.origin); + + int NumExistingDefenders = AITAC_GetNumPlayersOfTeamInArea(BotTeam, ThisStructure->Location, ThisDist - 10.0f, false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2); + + if (NumExistingDefenders < 2) + { + if (!StructureToDefend || ThisDist < MinDist) + { + StructureToDefend = ThisStructure; + MinDist = ThisDist; + } + } + } + + if (StructureToDefend) + { + AITASK_SetDefendTask(pBot, Task, StructureToDefend->edict, true); + return; + } + + AITASK_ClearBotTask(pBot, Task); } @@ -3844,6 +4213,8 @@ bool AlienCombatThink(AvHAIPlayer* pBot) if (pBot->CurrentCombatStrategy == COMBAT_STRATEGY_IGNORE) { return false; } + pBot->LastCombatTime = gpGlobals->time; + switch (pBot->Player->GetUser3()) { case AVH_USER3_ALIEN_PLAYER1: @@ -4166,7 +4537,6 @@ bool LerkCombatThink(AvHAIPlayer* pBot) { BotShootLocation(pBot, WEAPON_LERK_SPORES, SporeLocation); } - } else { diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.h b/main/source/mod/AIPlayers/AvHAIPlayer.h index 1ee9960b..f8712de7 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.h +++ b/main/source/mod/AIPlayers/AvHAIPlayer.h @@ -37,6 +37,7 @@ void BotShootLocation(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, const Vector void BombardierAttackTarget(AvHAIPlayer* pBot, edict_t* Target); void BotEvolveLifeform(AvHAIPlayer* pBot, Vector DesiredEvolveLocation, AvHMessageID TargetLifeform); +void BotEvolveUpgrade(AvHAIPlayer* pBot, Vector DesiredEvolveLocation, AvHMessageID TargetUpgrade); enemy_status* GetTrackedEnemyRefForTarget(AvHAIPlayer* pBot, edict_t* Target); @@ -106,6 +107,8 @@ bool ShouldAIPlayerTakeCommand(AvHAIPlayer* pBot); int BotGetNextEnemyTarget(AvHAIPlayer* pBot); +AvHMessageID AlienGetDesiredUpgrade(AvHAIPlayer* pBot, HiveTechStatus DesiredTech); + AvHAICombatStrategy GetBotCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy); AvHAICombatStrategy GetAlienCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy); AvHAICombatStrategy GetSkulkCombatStrategyForTarget(AvHAIPlayer* pBot, enemy_status* CurrentEnemy); diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp index 82d6182c..0bd06da8 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp @@ -593,7 +593,7 @@ void AIMGR_UpdateAIPlayers() UpdateBotChat(bot); - AIPlayerThink(bot); + CustomThink(bot); BotUpdateDesiredViewRotation(bot); } diff --git a/main/source/mod/AIPlayers/AvHAIPlayerUtil.cpp b/main/source/mod/AIPlayers/AvHAIPlayerUtil.cpp index f693c0be..98cf7d0b 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerUtil.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayerUtil.cpp @@ -641,6 +641,13 @@ bool PlayerHasWeapon(const AvHPlayer* Player, const AvHAIWeapon DesiredCombatWea // Marines don't have a fixed inventory, so we can just do a simple check for them. Same goes to confirm the alien has the weapon in their inventory if (IsPlayerMarine(Player) || !HasWeaponInInventory) { + if (DesiredCombatWeapon == WEAPON_MARINE_GRENADE && HasWeaponInInventory) + { + AvHBasePlayerWeapon* Weapon = dynamic_cast(Player->m_rgpPlayerItems[5]); + + return Weapon->m_iClip > 0; + } + return HasWeaponInInventory; } diff --git a/main/source/mod/AIPlayers/AvHAITactical.cpp b/main/source/mod/AIPlayers/AvHAITactical.cpp index d24e79c0..9ef8dfd8 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.cpp +++ b/main/source/mod/AIPlayers/AvHAITactical.cpp @@ -4199,4 +4199,31 @@ edict_t* AITAC_AlienFindNearestHealingSource(AvHTeamNumber Team, Vector SearchLo return (!FNullEnt(FriendlyGorge) ? FriendlyGorge : Result); +} + +bool AITAC_IsAlienUpgradeAvailableForTeam(AvHTeamNumber Team, HiveTechStatus DesiredTech) +{ + AvHAIDeployableStructureType SearchType; + + switch (DesiredTech) + { + case HIVE_TECH_DEFENCE: + SearchType = STRUCTURE_ALIEN_DEFENCECHAMBER; + break; + case HIVE_TECH_MOVEMENT: + SearchType = STRUCTURE_ALIEN_MOVEMENTCHAMBER; + break; + case HIVE_TECH_SENSORY: + SearchType = STRUCTURE_ALIEN_SENSORYCHAMBER; + break; + default: + return false; + } + + DeployableSearchFilter ChamberFilter; + ChamberFilter.DeployableTeam = Team; + ChamberFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + ChamberFilter.DeployableTypes = SearchType; + + return (AITAC_DeployableExistsAtLocation(ZERO_VECTOR, &ChamberFilter)); } \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAITactical.h b/main/source/mod/AIPlayers/AvHAITactical.h index 185cbcb8..bd3f9363 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.h +++ b/main/source/mod/AIPlayers/AvHAITactical.h @@ -175,4 +175,6 @@ void AITAC_OnTeamStartsModified(); edict_t* AITAC_AlienFindNearestHealingSource(AvHTeamNumber Team, Vector SearchLocation, edict_t* SearchingPlayer, bool bIncludeGorges); +bool AITAC_IsAlienUpgradeAvailableForTeam(AvHTeamNumber Team, HiveTechStatus DesiredTech); + #endif \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAITask.cpp b/main/source/mod/AIPlayers/AvHAITask.cpp index 46d6cf2b..c0ce8095 100644 --- a/main/source/mod/AIPlayers/AvHAITask.cpp +++ b/main/source/mod/AIPlayers/AvHAITask.cpp @@ -634,13 +634,24 @@ bool AITASK_IsAlienCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* } AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); + if (ResNodeIndex->bIsBaseNode) + { + if (FNullEnt(ResNodeIndex->ParentHive)) { return false; } // This is the marine base res node, leave it alone + + AvHAIHiveDefinition* ParentHive = AITAC_GetHiveFromEdict(ResNodeIndex->ParentHive); + + // An enemy is now building the hive that this res node belongs to, abort + if (ParentHive && ParentHive->OwningTeam == EnemyTeam) { return false; } + } + // Don't waste resources switching down to gorge if we're a lerk, fade or onos // but we can still clear the area of enemy structures if (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict)) { DeployableSearchFilter EnemyStructuresFilter; - EnemyStructuresFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam); + EnemyStructuresFilter.DeployableTeam = EnemyTeam; EnemyStructuresFilter.ReachabilityTeam = BotTeam; EnemyStructuresFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; EnemyStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); @@ -648,6 +659,27 @@ bool AITASK_IsAlienCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* return AITAC_DeployableExistsAtLocation(ResNodeIndex->Location, &EnemyStructuresFilter); } + if (!FNullEnt(ResNodeIndex->ParentHive)) + { + DeployableSearchFilter EnemyStructuresFilter; + EnemyStructuresFilter.DeployableTeam = EnemyTeam; + EnemyStructuresFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + EnemyStructuresFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + EnemyStructuresFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE) + { + EnemyStructuresFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); + } + else + { + EnemyStructuresFilter.DeployableTypes = (STRUCTURE_ALIEN_OFFENCECHAMBER); + } + + // Enemy has started fortifying the hive we want to build a RT in, abort + if (AITAC_DeployableExistsAtLocation(ResNodeIndex->Location, &EnemyStructuresFilter)) { return false; } + } + // We can attack structures basically if we aren't stuck with Gorge's spit attack bool bCanAttackStructures = (!IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB)); @@ -962,7 +994,7 @@ bool AITASK_IsAlienHealTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) // If our target is a player, give up if they are too far away. I'm not going to waste time chasing you around the map! float MaxHealRelevant = sqrf(UTIL_MetresToGoldSrcUnits(5.0f)); - return (vDist2DSq(pBot->CurrentFloorPosition, Task->TaskTarget->v.origin) <= MaxHealRelevant); + return (IsPlayerActiveInGame(Task->TaskTarget) && vDist2DSq(pBot->CurrentFloorPosition, Task->TaskTarget->v.origin) <= MaxHealRelevant); } bool AITASK_IsUseTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) diff --git a/main/source/mod/AvHMessage.h b/main/source/mod/AvHMessage.h index 95a20391..654fb5d0 100644 --- a/main/source/mod/AvHMessage.h +++ b/main/source/mod/AvHMessage.h @@ -182,7 +182,7 @@ typedef enum ALIEN_EVOLUTION_EIGHT = 108, // Adrenaline ALIEN_EVOLUTION_NINE = 109, // Silence ALIEN_EVOLUTION_TEN = 110, // Cloaking - ALIEN_EVOLUTION_ELEVEN = 111, // Pheromones + ALIEN_EVOLUTION_ELEVEN = 111, // Focus ALIEN_EVOLUTION_TWELVE = 112, // Scent of fear // Alien lifeforms