diff --git a/main/source/mod/AIPlayers/AvHAICommander.cpp b/main/source/mod/AIPlayers/AvHAICommander.cpp index 459b9a72..4179144a 100644 --- a/main/source/mod/AIPlayers/AvHAICommander.cpp +++ b/main/source/mod/AIPlayers/AvHAICommander.cpp @@ -7,6 +7,7 @@ #include "AvHAINavigation.h" #include "AvHAITask.h" #include "AvHAIHelper.h" +#include "AvHAIPlayerManager.h" #include "../AvHSharedUtil.h" #include "../AvHServerUtil.h" @@ -170,6 +171,373 @@ bool AICOMM_IssueBuildOrder(AvHAIPlayer* pBot, edict_t* Recipient, edict_t* Targ return true; } +void AICOMM_AssignNewPlayerOrder(AvHAIPlayer* pBot, edict_t* Assignee, edict_t* TargetEntity, AvHAIOrderPurpose OrderPurpose) +{ + if (FNullEnt(Assignee) || FNullEnt(TargetEntity) || OrderPurpose == ORDERPURPOSE_NONE) { return; } + + // Clear any existing order we have for this player + for (auto it = pBot->ActiveOrders.begin(); it != pBot->ActiveOrders.end();) + { + if (it->Assignee == Assignee) + { + it = pBot->ActiveOrders.erase(it); + } + else + { + it++; + } + } + + ai_commander_order NewOrder; + NewOrder.Assignee = Assignee; + NewOrder.OrderTarget = TargetEntity; + NewOrder.OrderPurpose = OrderPurpose; + + AICOMM_IssueOrderForAssignedJob(pBot, &NewOrder); + + pBot->ActiveOrders.push_back(NewOrder); +} + +void AICOMM_IssueOrderForAssignedJob(AvHAIPlayer* pBot, ai_commander_order* Order) +{ + if (Order->OrderPurpose == ORDERPURPOSE_SIEGE_HIVE || Order->OrderPurpose == ORDERPURPOSE_SECURE_HIVE) + { + bool bIsSiegeHiveOrder = (Order->OrderPurpose == ORDERPURPOSE_SIEGE_HIVE); + const AvHAIHiveDefinition* Hive = AITAC_GetHiveFromEdict(Order->OrderTarget); + + if (Hive) + { + Vector OrderLocation = Hive->FloorLocation; + + DeployableSearchFilter StructureFilter; + StructureFilter.DeployableTeam = pBot->Player->GetTeam(); + StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + StructureFilter.MaxSearchRadius = (bIsSiegeHiveOrder) ? UTIL_MetresToGoldSrcUnits(25.0f) : UTIL_MetresToGoldSrcUnits(10.0f); + + AvHAIBuildableStructure* NearestToHive = AITAC_FindClosestDeployableToLocation(Hive->Location, &StructureFilter); + + if (NearestToHive) + { + if (!(NearestToHive->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) + { + AICOMM_IssueBuildOrder(pBot, Order->Assignee, NearestToHive->edict); + Order->LastReminderTime = gpGlobals->time; + Order->LastPlayerDistance = vDist2DSq(Order->Assignee->v.origin, NearestToHive->Location); + Order->OrderLocation = NearestToHive->Location; + return; + } + else + { + Vector MoveLoc = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestToHive->Location, UTIL_MetresToGoldSrcUnits(3.0f)); + + AICOMM_IssueMovementOrder(pBot, Order->Assignee, MoveLoc); + Order->LastReminderTime = gpGlobals->time; + Order->LastPlayerDistance = vDist2DSq(Order->Assignee->v.origin, MoveLoc); + Order->OrderLocation = MoveLoc; + return; + } + } + else + { + Vector MoveLoc = (bIsSiegeHiveOrder) ? UTIL_GetRandomPointOnNavmeshInDonutIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Hive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), UTIL_MetresToGoldSrcUnits(25.0f)) : Hive->FloorLocation; + + AICOMM_IssueMovementOrder(pBot, Order->Assignee, MoveLoc); + Order->LastReminderTime = gpGlobals->time; + Order->LastPlayerDistance = vDist2DSq(Order->Assignee->v.origin, MoveLoc); + Order->OrderLocation = MoveLoc; + return; + } + } + + Order->LastReminderTime = gpGlobals->time; + return; + } + + if (Order->OrderPurpose == ORDERPURPOSE_SECURE_RESNODE) + { + const AvHAIResourceNode* ResNode = AITAC_GetResourceNodeFromEdict(Order->OrderTarget); + + if (ResNode) + { + if (ResNode->OwningTeam == pBot->Player->GetTeam() && ResNode->ActiveTowerEntity && !UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity)) + { + AICOMM_IssueBuildOrder(pBot, Order->Assignee, ResNode->ActiveTowerEntity); + Order->LastReminderTime = gpGlobals->time; + Order->LastPlayerDistance = vDist2DSq(Order->Assignee->v.origin, ResNode->Location); + Order->OrderLocation = ResNode->Location; + return; + } + + Vector MoveLoc = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), ResNode->Location, UTIL_MetresToGoldSrcUnits(3.0f)); + + AICOMM_IssueMovementOrder(pBot, Order->Assignee, MoveLoc); + Order->LastReminderTime = gpGlobals->time; + Order->LastPlayerDistance = vDist2DSq(Order->Assignee->v.origin, MoveLoc); + Order->OrderLocation = MoveLoc; + return; + + } + + return; + } +} + +int AICOMM_GetNumPlayersAssignedToOrder(AvHAIPlayer* pBot, edict_t* TargetEntity, AvHAIOrderPurpose OrderPurpose) +{ + int Result = 0; + + for (auto it = pBot->ActiveOrders.begin(); it != pBot->ActiveOrders.end(); it++) + { + if (it->OrderTarget == TargetEntity && it->OrderPurpose == OrderPurpose) + { + Result++; + } + } + + return Result; +} + +bool AICOMM_IsOrderStillValid(AvHAIPlayer* pBot, ai_commander_order* Order) +{ + if (FNullEnt(Order->Assignee) || FNullEnt(Order->OrderTarget) || !IsPlayerActiveInGame(Order->Assignee) || Order->OrderPurpose == ORDERPURPOSE_NONE) { return false; } + + switch (Order->OrderPurpose) + { + case ORDERPURPOSE_SECURE_HIVE: + { + const AvHAIHiveDefinition* Hive = AITAC_GetHiveFromEdict(Order->OrderTarget); + + if (!Hive || Hive->Status != HIVE_STATUS_UNBUILT) { return false; } + + return !AICOMM_IsHiveFullySecured(pBot, Hive, false); + } + break; + case ORDERPURPOSE_SIEGE_HIVE: + { + const AvHAIHiveDefinition* Hive = AITAC_GetHiveFromEdict(Order->OrderTarget); + + // Hive has been destroyed, no longer needs sieging + if (!Hive || Hive->Status == HIVE_STATUS_UNBUILT) { return false; } + + DeployableSearchFilter StructureFilter; + StructureFilter.DeployableTeam = pBot->Player->GetTeam(); + StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(25.0f); + + // Check that any siege structure exists. This will avoid situations where commander keeps ordering marines to a hive that is too well defended + bool bSiegeStructureExists = AITAC_DeployableExistsAtLocation(Hive->Location, &StructureFilter); + + return bSiegeStructureExists; + + } + break; + case ORDERPURPOSE_SECURE_RESNODE: + { + const AvHAIResourceNode* ResNode = AITAC_GetResourceNodeFromEdict(Order->OrderTarget); + + if (!ResNode) { return false; } + + return (ResNode->OwningTeam != pBot->Player->GetTeam() || !ResNode->ActiveTowerEntity || !UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity)); + + } + break; + default: + return false; + } + + return false; +} + +bool AICOMM_DoesPlayerOrderNeedReminder(AvHAIPlayer* pBot, ai_commander_order* Order) +{ + float NewDist = vDist2DSq(Order->Assignee->v.origin, Order->OrderLocation); + float OldDist = Order->LastPlayerDistance; + Order->LastPlayerDistance = NewDist; + + if (gpGlobals->time - Order->LastReminderTime < MIN_COMMANDER_REMIND_TIME) { return false; } + + if (Order->OrderPurpose == ORDERPURPOSE_SECURE_RESNODE) + { + if (vDist2DSq(Order->Assignee->v.origin, Order->OrderTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { return false; } + } + + if (Order->OrderPurpose == ORDERPURPOSE_SIEGE_HIVE) + { + if (vDist2DSq(Order->Assignee->v.origin, Order->OrderTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(25.0f))) { return false; } + } + + if (Order->OrderPurpose == ORDERPURPOSE_SECURE_HIVE) + { + if (vDist2DSq(Order->Assignee->v.origin, Order->OrderTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) { return false; } + } + + return NewDist >= OldDist; +} + +void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) +{ + // Clear out any orders which aren't relevant any more + for (auto it = pBot->ActiveOrders.begin(); it != pBot->ActiveOrders.end();) + { + if (!AICOMM_IsOrderStillValid(pBot, &(*it))) + { + it = pBot->ActiveOrders.erase(it); + } + else + { + // If the person we're ordering around isn't doing as they're told, then issue them a reminder + if (AICOMM_DoesPlayerOrderNeedReminder(pBot, &(*it))) + { + AICOMM_IssueOrderForAssignedJob(pBot, &(*it)); + } + + it++; + } + } + + int NumPlayersOnTeam = AITAC_GetNumActivePlayersOnTeam(pBot->Player->GetTeam()); + int DesiredPlayers = imini(2, (int)ceilf((float)NumPlayersOnTeam *0.5f)); + + const AvHAIHiveDefinition* SiegedHive = AITAC_GetNearestHiveUnderActiveSiege(pBot->Player->GetTeam(), AITAC_GetCommChairLocation(pBot->Player->GetTeam())); + + if (SiegedHive) + { + int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, SiegedHive->HiveEntity->edict(), ORDERPURPOSE_SIEGE_HIVE); + int NumSiegingPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, SiegedHive->HiveEntity->edict(), ORDERPURPOSE_SIEGE_HIVE); + + if ((NumAssignedPlayers + NumSiegingPlayers) < DesiredPlayers) + { + for (int i = 0; i < DesiredPlayers - (NumAssignedPlayers + NumSiegingPlayers); i++) + { + edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, SiegedHive->FloorLocation); + + if (!FNullEnt(NewAssignee)) + { + AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, SiegedHive->HiveEntity->edict(), ORDERPURPOSE_SIEGE_HIVE); + } + } + } + } + + vector Hives = AITAC_GetAllHives(); + + AvHAIHiveDefinition* EmptyHive = nullptr; + float MinDist = 0.0f; + + for (auto it = Hives.begin(); it != Hives.end(); it++) + { + AvHAIHiveDefinition* ThisHive = (*it); + if (ThisHive->Status != HIVE_STATUS_UNBUILT) { continue; } + if (AICOMM_IsHiveFullySecured(pBot, ThisHive, false)) { continue; } + + int NumPlayersSecuring = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, ThisHive->HiveEntity->edict(), ORDERPURPOSE_SECURE_HIVE); + + if ((NumPlayersSecuring + NumAssignedPlayers) < DesiredPlayers) + { + float ThisDist = vDist2DSq(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), ThisHive->Location); + + if (!EmptyHive || ThisDist < MinDist) + { + EmptyHive = ThisHive; + MinDist = ThisDist; + } + } + } + + if (EmptyHive) + { + edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, EmptyHive->FloorLocation); + + if (!FNullEnt(NewAssignee)) + { + AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, EmptyHive->HiveEntity->edict(), ORDERPURPOSE_SECURE_HIVE); + } + } + + DeployableSearchFilter ResNodeFilter; + ResNodeFilter.DeployableTeam = TEAM_IND; + ResNodeFilter.ReachabilityTeam = pBot->Player->GetTeam(); + ResNodeFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + + const AvHAIResourceNode* ResNode = AITAC_FindNearestResourceNodeToLocation(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), &ResNodeFilter); + + if (ResNode) + { + int NumPlayersSecuring = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ResNode->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + int NumAssignedPlayers = AICOMM_GetNumPlayersAssignedToOrder(pBot, ResNode->ResourceEntity->edict(), ORDERPURPOSE_SECURE_RESNODE); + + if ((NumPlayersSecuring + NumAssignedPlayers) < 1) + { + edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, ResNode->Location); + + if (!FNullEnt(NewAssignee)) + { + AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, ResNode->ResourceEntity->edict(), ORDERPURPOSE_SECURE_RESNODE); + } + } + } + + +} + +edict_t* AICOMM_GetPlayerWithNoOrderNearestLocation(AvHAIPlayer* pBot, Vector SearchLocation) +{ + edict_t* Result = nullptr; + float MinDist = 0.0f; + + vector PlayerList = AIMGR_GetAllPlayersOnTeam(pBot->Player->GetTeam()); + + // First, remove all players who are dead or otherwise not active with boots on the ground (e.g. commander, or being digested) + for (auto it = PlayerList.begin(); it != PlayerList.end();) + { + AvHPlayer* PlayerRef = (*it); + + if (!IsPlayerActiveInGame(PlayerRef->edict())) + { + it = PlayerList.erase(it); + } + else + { + it++; + } + } + + // Next, erase all players with orders so we only have a list of players without orders assigned to them + for (auto it = pBot->ActiveOrders.begin(); it != pBot->ActiveOrders.end(); it++) + { + AvHPlayer* ThisPlayer = dynamic_cast(CBaseEntity::Instance(it->Assignee)); + + if (!ThisPlayer) { continue; } + + std::vector::iterator FoundPlayer = std::find(PlayerList.begin(), PlayerList.end(), ThisPlayer); + + if (FoundPlayer != PlayerList.end()) + { + PlayerList.erase(FoundPlayer); + } + } + + // Now rank them by distance and return the result + for (auto it = PlayerList.begin(); it != PlayerList.end(); it++) + { + edict_t* PlayerEdict = (*it)->edict(); + + float ThisDist = vDist2DSq(PlayerEdict->v.origin, SearchLocation); + + if (!Result || ThisDist < MinDist) + { + Result = PlayerEdict; + MinDist = ThisDist; + } + } + + return Result; + +} + bool AICOMM_IssueSecureHiveOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIHiveDefinition* HiveToSecure) { if (!HiveToSecure || FNullEnt(Recipient) || !IsPlayerActiveInGame(Recipient)) { return false; } @@ -510,6 +878,8 @@ bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action) } } + if (pBot->Player->GetResources() < 30) { return false; } + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB; StructureFilter.MaxSearchRadius = 0.0f; @@ -944,6 +1314,12 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit if (vIsZero(NextBuildPosition)) { NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f)); + + if (vIsZero(NextBuildPosition)) + { + // Fall-back, this could end up putting the structure in dodgy spots but better than not placing it at all + NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f)); + } } if (!ExistingPG) @@ -953,6 +1329,7 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit if (!ExistingTF) { + if (vDist2DSq(NextBuildPosition, HiveToSiege->Location) > sqrf(UTIL_MetresToGoldSrcUnits(20.0f))) { return true; } return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); } @@ -980,13 +1357,22 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit { SiegeLocation = ExistingTF->Location; - NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f)); + NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(5.0f)); if (vIsZero(NextBuildPosition)) { + // Reduce radius to avoid putting it on the other side of a wall or something NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f)); + + if (vIsZero(NextBuildPosition)) + { + // Fall-back, this could end up putting the structure in dodgy spots but better than not placing it at all + NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + } } + // Don't put the turret out of siege range + if (vDist2DSq(NextBuildPosition, HiveToSiege->Location) > sqrf(kSiegeTurretRange)) { return true; } return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_SIEGETURRET, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); } @@ -1037,7 +1423,12 @@ bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefini if (!ExistingPG) { - Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + + if (vIsZero(BuildLocation)) + { + BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + } if (!vIsZero(BuildLocation)) { @@ -1057,7 +1448,12 @@ bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefini if (!ExistingTF) { - Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(3.0f)); + + if (vIsZero(BuildLocation)) + { + BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + } if (!vIsZero(BuildLocation)) { @@ -1440,6 +1836,8 @@ void AICOMM_CommanderThink(AvHAIPlayer* pBot) if (gpGlobals->time < pBot->next_commander_action_time) { return; } + AICOMM_UpdatePlayerOrders(pBot); + if (AICOMM_CheckForNextRecycleAction(pBot)) { return; } if (AICOMM_CheckForNextSupportAction(pBot)) { return; } if (AICOMM_CheckForNextBuildAction(pBot, &pBot->BuildAction)) { return; } @@ -1479,6 +1877,15 @@ bool AICOMM_IsCommanderActionValid(AvHAIPlayer* pBot, commander_action* Action) bool AICOMM_ShouldCommanderLeaveChair(AvHAIPlayer* pBot) { + if (pBot->BotRole != BOT_ROLE_COMMAND) { return true; } + + if (AIMGR_GetCommanderMode() == COMMANDERMODE_DISABLED) { return true; } + + if (AIMGR_GetCommanderMode() == COMMANDERMODE_IFNOHUMAN) + { + if (AIMGR_GetNumHumanPlayersOnTeam(pBot->Player->GetTeam()) > 0) { return true;} + } + int NumAliveMarinesInBase = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), AITAC_GetCommChairLocation(pBot->Player->GetTeam()), UTIL_MetresToGoldSrcUnits(30.0f), true, pBot->Edict, AVH_USER3_NONE); if (NumAliveMarinesInBase > 0) { return false; } @@ -1524,48 +1931,30 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl if (Hive->Status != HIVE_STATUS_UNBUILT) { continue; } - if (AICOMM_IsHiveFullySecured(CommanderBot, Hive)) { continue; } + if (AICOMM_IsHiveFullySecured(CommanderBot, Hive, true)) { continue; } - if (AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, Hive->Location, UTIL_MetresToGoldSrcUnits(10.0f)) == nullptr) { continue; } + Vector SecureLocation = Hive->FloorLocation; - if (AITAC_AnyPlayerOnTeamWithLOS(CommanderTeam, Hive->Location, UTIL_MetresToGoldSrcUnits(10.0f))) + DeployableSearchFilter StructureFilter; + StructureFilter.DeployableTeam = CommanderTeam; + StructureFilter.ReachabilityTeam = CommanderTeam; + StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); + + AvHAIBuildableStructure* ExistingStructure = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter); + + if (ExistingStructure && UTIL_QuickTrace(nullptr, UTIL_GetCentreOfEntity(ExistingStructure->edict), Hive->Location)) { - DeployableSearchFilter StructureFilter; - StructureFilter.DeployableTeam = CommanderTeam; - StructureFilter.ReachabilityTeam = CommanderTeam; - StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; - StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; - - StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; - StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); - - AvHAIBuildableStructure* PG = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter); - - bool bCanSeePG = (!PG || AITAC_AnyPlayerOnTeamWithLOS(CommanderTeam, UTIL_GetCentreOfEntity(PG->edict), UTIL_MetresToGoldSrcUnits(10.0f))); - - if (!bCanSeePG) - { - - StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY; - - AvHAIBuildableStructure* TF = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter); - - bool bNeedsElectrifying = false; - - if (TF) - { - StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRET; - - bNeedsElectrifying = (UTIL_StructureIsFullyBuilt(TF->edict) && !UTIL_IsStructureElectrified(TF->edict) && AITAC_DeployableExistsAtLocation(TF->Location, &StructureFilter)); - } - - bool bCanSeeTF = (!TF || AITAC_AnyPlayerOnTeamWithLOS(CommanderTeam, UTIL_GetCentreOfEntity(TF->edict), UTIL_MetresToGoldSrcUnits(10.0f))); - - if (!bNeedsElectrifying && !bCanSeePG && !bCanSeeTF) { continue; } - } - + SecureLocation = ExistingStructure->Location; } + float MarineDist = (ExistingStructure) ? UTIL_MetresToGoldSrcUnits(5.0f) : UTIL_MetresToGoldSrcUnits(10.0f); + + if (AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, SecureLocation, MarineDist) == nullptr) { continue; } + float ThisDist = vDist2DSq(Hive->FloorLocation, SearchLocation); if (!Result || ThisDist < MinDist) @@ -1579,7 +1968,7 @@ const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPl return Result; } -bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinition* Hive) +bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinition* Hive, bool bIncludeElectrical) { AvHTeamNumber CommanderTeam = CommanderBot->Player->GetTeam(); @@ -1595,6 +1984,7 @@ bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinit SearchFilter.DeployableTeam = CommanderTeam; SearchFilter.ReachabilityTeam = CommanderTeam; SearchFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + SearchFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; SearchFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; SearchFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); @@ -1604,7 +1994,7 @@ bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinit { AvHAIBuildableStructure* Structure = (*it); - if (Structure->StructureType == STRUCTURE_MARINE_TURRETFACTORY) + if (Structure->StructureType == STRUCTURE_MARINE_PHASEGATE) { bHasPhaseGate = true; } @@ -1619,16 +2009,13 @@ bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinit NumTurrets = AITAC_GetNumDeployablesNearLocation(Structure->Location, &SearchFilter); - } } const AvHAIResourceNode* ResNode = Hive->HiveResNodeRef; - bool bSecuredResNode = (!ResNode || (ResNode->bIsOccupied && ResNode->OwningTeam == CommanderTeam)); + bool bSecuredResNode = (!ResNode || (ResNode->bIsOccupied && ResNode->OwningTeam == CommanderTeam && UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity))); - bool bShouldElectrifyResNode = (ResNode && bSecuredResNode && CommanderBot->Player->GetResources() > 100 && AITAC_ElectricalResearchIsAvailable(ResNode->ActiveTowerEntity)); - - return ((!bPhaseGatesAvailable || bHasPhaseGate) && bHasTurretFactory && bTurretFactoryElectrified && NumTurrets >= 5 && bSecuredResNode && !bShouldElectrifyResNode); + return ((!bPhaseGatesAvailable || bHasPhaseGate) && bHasTurretFactory && (!bIncludeElectrical || bTurretFactoryElectrified) && NumTurrets >= 5 && bSecuredResNode); } \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAICommander.h b/main/source/mod/AIPlayers/AvHAICommander.h index 5241a547..f1a33728 100644 --- a/main/source/mod/AIPlayers/AvHAICommander.h +++ b/main/source/mod/AIPlayers/AvHAICommander.h @@ -12,6 +12,8 @@ #include "AvHAIConstants.h" +static const float MIN_COMMANDER_REMIND_TIME = 20.0f; // How frequently the commander can nag a player to do something, if they don't think they're doing it + bool AICOMM_DeployStructure(AvHAIPlayer* pBot, const AvHAIDeployableStructureType StructureToDeploy, const Vector Location, StructurePurpose Purpose = STRUCTURE_PURPOSE_NONE); bool AICOMM_DeployItem(AvHAIPlayer* pBot, const AvHAIDeployableItemType ItemToDeploy, const Vector Location); bool AICOMM_UpgradeStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToUpgrade); @@ -19,11 +21,19 @@ bool AICOMM_ResearchTech(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureTo bool AICOMM_RecycleStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToRecycle); bool AICOMM_IssueMovementOrder(AvHAIPlayer* pBot, edict_t* Recipient, const Vector MoveLocation); -bool AICOMM_IssueBuildOrder(AvHAIPlayer* pBot, edict_t* Recipient, edict_t* TargetStructure); +bool AICOMM_IssueBuildOrder(AvHAIPlayer* pBot, edict_t* Recipient, edict_t* TargetStructuree); bool AICOMM_IssueSecureHiveOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIHiveDefinition* HiveToSecure); bool AICOMM_IssueSiegeHiveOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIHiveDefinition* HiveToSiege, const Vector SiegePosition); bool AICOMM_IssueSecureResNodeOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIResourceNode* ResNode); +void AICOMM_AssignNewPlayerOrder(AvHAIPlayer* pBot, edict_t* Assignee, edict_t* TargetEntity, AvHAIOrderPurpose OrderPurpose); +int AICOMM_GetNumPlayersAssignedToOrder(AvHAIPlayer* pBot, edict_t* TargetEntity, AvHAIOrderPurpose OrderPurpose); +bool AICOMM_IsOrderStillValid(AvHAIPlayer* pBot, ai_commander_order* Order); +void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot); +edict_t* AICOMM_GetPlayerWithNoOrderNearestLocation(AvHAIPlayer* pBot, Vector SearchLocation); +bool AICOMM_DoesPlayerOrderNeedReminder(AvHAIPlayer* pBot, ai_commander_order* Order); +void AICOMM_IssueOrderForAssignedJob(AvHAIPlayer* pBot, ai_commander_order* Order); + void AICOMM_ClearAction(commander_action* Action); bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action); bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot); @@ -46,7 +56,7 @@ ai_commander_request* AICOMM_GetExistingRequestForPlayer(AvHAIPlayer* pBot, edic void AICOMM_CheckNewRequests(AvHAIPlayer* pBot); bool AICOMM_IsRequestValid(ai_commander_request* Request); -bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinition* Hive); +bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinition* Hive, bool bIncludeElectrical); bool AICOMM_ShouldCommanderLeaveChair(AvHAIPlayer* pBot); diff --git a/main/source/mod/AIPlayers/AvHAIConfig.h b/main/source/mod/AIPlayers/AvHAIConfig.h index a8749bae..59e31283 100644 --- a/main/source/mod/AIPlayers/AvHAIConfig.h +++ b/main/source/mod/AIPlayers/AvHAIConfig.h @@ -14,15 +14,6 @@ typedef enum _BOTFILLMODE } BotFillMode; -// Bot commander mode, should the bot go commander and when -typedef enum _COMMANDERMODE -{ - COMMANDERMODE_NEVER = 0, // Bot never tries to command - COMMANDERMODE_IFNOHUMAN, // Bot only commands if no human is on the marine team - COMMANDERMODE_ALWAYS // Bot will always take command if no human does after CommanderWaitTime expires - -} CommanderMode; - // Each map can have a desired marine and alien team size typedef struct _TEAMSIZEDEFINITIONS { diff --git a/main/source/mod/AIPlayers/AvHAIConstants.h b/main/source/mod/AIPlayers/AvHAIConstants.h index 113f913c..a60c9bf0 100644 --- a/main/source/mod/AIPlayers/AvHAIConstants.h +++ b/main/source/mod/AIPlayers/AvHAIConstants.h @@ -171,6 +171,36 @@ typedef enum _STRUCTUREPURPOSE } StructurePurpose; +typedef enum _AVHAICOMMANDERMODE +{ + COMMANDERMODE_DISABLED, // AI Commander not allowed + COMMANDERMODE_IFNOHUMAN, // AI Commander only allowed if no humans are on the marine team + COMMANDERMODE_ENABLED // AI Commander allowed if no human takes charge (following grace period) +} AvHAICommanderMode; + +// Bot's role on the team. For marines, this only governs what they do when left to their own devices. +// Marine bots will always listen to orders from the commander regardless of role. +typedef enum _AVHAIBOTROLE +{ + BOT_ROLE_NONE, // No defined role + + // General Roles + + BOT_ROLE_FIND_RESOURCES, // Will hunt for uncapped resource nodes and cap them. Will attack enemy resource towers + BOT_ROLE_SWEEPER, // Defensive role to protect infrastructure and build at base. Will patrol to keep outposts secure + BOT_ROLE_ASSAULT, // Will go to attack the hive and other alien structures + + // Marine-only Roles + + BOT_ROLE_COMMAND, // Will attempt to take command + BOT_ROLE_BOMBARDIER, // Bot is armed with a GL and wants to wreck your shit + + // Alien-only roles + + BOT_ROLE_BUILDER, // Will focus on building chambers and hives. Stays gorge most of the time + BOT_ROLE_HARASS // Focuses on taking down enemy resource nodes and hunting the enemy +} AvHAIBotRole; + typedef struct _OFF_MESH_CONN { unsigned int ConnectionRefs[2]; @@ -349,27 +379,6 @@ typedef enum } BotAttackResult; -// Bot's role on the team. For marines, this only governs what they do when left to their own devices. -// Marine bots will always listen to orders from the commander regardless of role. -enum BotRole -{ - BOT_ROLE_NONE, // No defined role - - // Marine Roles - - BOT_ROLE_COMMAND, // Will attempt to take command - BOT_ROLE_FIND_RESOURCES, // Will hunt for uncapped resource nodes and cap them. Will attack enemy resource towers - BOT_ROLE_SWEEPER, // Defensive role to protect infrastructure and build at base. Will patrol to keep outposts secure - BOT_ROLE_ASSAULT, // Will go to attack the hive and other alien structures - BOT_ROLE_BOMBARDIER, // Bot is armed with a GL and wants to wreck your shit - - // Alien roles - - BOT_ROLE_RES_CAPPER, // Will hunt for uncapped nodes or ones held by the enemy and cap them - BOT_ROLE_BUILDER, // Will focus on building chambers and hives. Stays gorge most of the time - BOT_ROLE_HARASS, // Focuses on taking down enemy resource nodes and hunting the enemy - BOT_ROLE_DESTROYER // Will go fade/onos when it can, focuses on attacking critical infrastructure -}; // Bot path node. A path will be several of these strung together to lead the bot to its destination typedef struct _BOT_PATH_NODE @@ -541,6 +550,24 @@ typedef struct _COMMANDER_ACTION } commander_action; +typedef enum +{ + ORDERPURPOSE_NONE, + ORDERPURPOSE_SECURE_HIVE, + ORDERPURPOSE_SIEGE_HIVE, + ORDERPURPOSE_SECURE_RESNODE +} AvHAIOrderPurpose; + +typedef struct _AI_COMMANDER_ORDER +{ + edict_t* Assignee = nullptr; + AvHAIOrderPurpose OrderPurpose = ORDERPURPOSE_NONE; + edict_t* OrderTarget = nullptr; + Vector OrderLocation = g_vecZero; + float LastReminderTime = 0.0f; + float LastPlayerDistance = 0.0f; +} ai_commander_order; + typedef struct _AI_COMMANDER_REQUEST { bool bNewRequest = false; // Is this a new request just come in? @@ -593,6 +620,8 @@ typedef struct AVH_AI_PLAYER AvHAIPlayerTask WantsAndNeedsTask; AvHAIPlayerTask CommanderTask; // Task assigned by the commander + float BotNextTaskEvaluationTime = 0.0f; + bot_skill BotSkillSettings; char PathStatus[128]; // Debug used to help figure out what's going on with a bot's path finding @@ -607,6 +636,7 @@ typedef struct AVH_AI_PLAYER commander_action* CurrentAction; vector ActiveRequests; + vector ActiveOrders; float next_commander_action_time = 0.0f; @@ -633,6 +663,9 @@ typedef struct AVH_AI_PLAYER Vector ViewForwardVector = g_vecZero; // Bot's current forward unit vector Vector LastSafeLocation = g_vecZero; + AvHAIBotRole BotRole = BOT_ROLE_NONE; + + } AvHAIPlayer; diff --git a/main/source/mod/AIPlayers/AvHAIHelper.cpp b/main/source/mod/AIPlayers/AvHAIHelper.cpp index 897a8f34..146b761a 100644 --- a/main/source/mod/AIPlayers/AvHAIHelper.cpp +++ b/main/source/mod/AIPlayers/AvHAIHelper.cpp @@ -426,4 +426,47 @@ void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, int r, int g, int WRITE_BYTE(250); // brightness WRITE_BYTE(5); // speed MESSAGE_END(); +} + +void UTIL_DrawHUDText(edict_t* pEntity, char channel, float x, float y, unsigned char r, unsigned char g, unsigned char b, const char* string) +{ + + + // higher level wrapper for hudtextparms TE_TEXTMESSAGEs. This function is meant to be called + // every frame, since the duration of the display is roughly worth the duration of a video + // frame. The X and Y coordinates are unary fractions which are bound to this rule: + // 0: top of the screen (Y) or left of the screen (X), left aligned text + // 1: bottom of the screen (Y) or right of the screen (X), right aligned text + // -1(only one negative value possible): center of the screen (X and Y), centered text + // Any value ranging from 0 to 1 will represent a valid position on the screen. + + //static short duration; + + if (FNullEnt(pEntity)) { return; } + + //duration = (int)GAME_GetServerMSecVal() * 256 / 750; // compute text message duration + //if (duration < 5) + // duration = 5; + + MESSAGE_BEGIN(MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, pEntity); + WRITE_BYTE(TE_TEXTMESSAGE); + WRITE_BYTE(channel); // channel + WRITE_SHORT((int)(x * 8192.0f)); // x coordinates * 8192 + WRITE_SHORT((int)(y * 8192.0f)); // y coordinates * 8192 + WRITE_BYTE(0); // effect (fade in/out) + WRITE_BYTE(r); // initial RED + WRITE_BYTE(g); // initial GREEN + WRITE_BYTE(b); // initial BLUE + WRITE_BYTE(1); // initial ALPHA + WRITE_BYTE(r); // effect RED + WRITE_BYTE(g); // effect GREEN + WRITE_BYTE(b); // effect BLUE + WRITE_BYTE(1); // effect ALPHA + WRITE_SHORT(0); // fade-in time in seconds * 256 + WRITE_SHORT(0); // fade-out time in seconds * 256 + WRITE_SHORT(1); // hold time in seconds * 256 + WRITE_STRING(string);//string); // send the string + MESSAGE_END(); // end + + return; } \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAIHelper.h b/main/source/mod/AIPlayers/AvHAIHelper.h index fc1df6e3..01528049 100644 --- a/main/source/mod/AIPlayers/AvHAIHelper.h +++ b/main/source/mod/AIPlayers/AvHAIHelper.h @@ -44,4 +44,6 @@ void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, int r, int g, int // Draws a coloured line using RGB input, between start and end for the given player (pEntity) for given number of seconds void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, float drawTimeSeconds, int r, int g, int b); +void UTIL_DrawHUDText(edict_t* pEntity, char channel, float x, float y, unsigned char r, unsigned char g, unsigned char b, const char* string); + #endif \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAINavigation.cpp b/main/source/mod/AIPlayers/AvHAINavigation.cpp index 90894570..1aaf2849 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.cpp +++ b/main/source/mod/AIPlayers/AvHAINavigation.cpp @@ -468,7 +468,7 @@ void UTIL_AddStructureTemporaryObstacles(AvHAIBuildableStructure* Structure) } // Always cut a hole in the building nav mesh so we don't try to place anything on top of this structure in future - unsigned int NewObstacleRef = UTIL_AddTemporaryObstacle(BUILDING_NAV_MESH, UTIL_GetCentreOfEntity(Structure->edict), Radius * 1.5f, 100.0f, DT_TILECACHE_NULL_AREA); + unsigned int NewObstacleRef = UTIL_AddTemporaryObstacle(BUILDING_NAV_MESH, UTIL_GetCentreOfEntity(Structure->edict), Radius * 1.1f, 100.0f, DT_TILECACHE_NULL_AREA); if (NewObstacleRef > 0) { diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.cpp b/main/source/mod/AIPlayers/AvHAIPlayer.cpp index ea2c3a83..251544e5 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayer.cpp @@ -9,7 +9,9 @@ #include "AvHAITactical.h" #include "AvHAITask.h" #include "AvHAICommander.h" +#include "AvHAIPlayerManager.h" +#include "../AvHGamerules.h" #include "../AvHMessage.h" extern nav_mesh NavMeshes[MAX_NAV_MESHES]; // Array of nav meshes. Currently only 3 are used (building, onos, and regular) @@ -1511,6 +1513,546 @@ void DroneThink(AvHAIPlayer* pBot) //AIDEBUG_DrawBotPath(pBot); } +void SetNewAIPlayerRole(AvHAIPlayer* pBot, AvHAIBotRole NewRole) +{ + if (NewRole != pBot->BotRole) + { + AITASK_ClearBotTask(pBot, &pBot->PrimaryBotTask); + AITASK_ClearBotTask(pBot, &pBot->SecondaryBotTask); + + pBot->BotRole = NewRole; + } +} + +void UpdateAIPlayerCORole(AvHAIPlayer* pBot) +{ + +} + +void UpdateAIPlayerDMRole(AvHAIPlayer* pBot) +{ + +} + +void UpdateAIAlienPlayerNSRole(AvHAIPlayer* pBot) +{ + +} + +bool ShouldAIPlayerTakeCommand(AvHAIPlayer* pBot) +{ + AvHAICommanderMode CurrentCommanderMode = AIMGR_GetCommanderMode(); + + // Don't go commander if bots are not allowed to + if (CurrentCommanderMode == COMMANDERMODE_DISABLED) { return false; } + + AvHTeamNumber BotTeamNumber = pBot->Player->GetTeam(); + AvHTeam* BotTeam = GetGameRules()->GetTeam(BotTeamNumber); + + // Don't go commander if we're not an alien. You never know with the way I structure my logic... + if (!BotTeam || BotTeam->GetTeamType() != AVH_CLASS_TYPE_MARINE) { return false; } + + // Don't go commander if we're only supposed to command when there aren't any humans and we have one + if (CurrentCommanderMode == COMMANDERMODE_IFNOHUMAN && AIMGR_GetNumHumanPlayersOnTeam(BotTeamNumber) > 0) { return false; } + + AvHPlayer* CurrentCommander = BotTeam->GetCommanderPlayer(); + + // Don't go commander if we already have one, and it's not us + if (CurrentCommander) + { + return CurrentCommander == pBot->Player; + } + + // Don't go commander if there is another bot already taking command + if (AIMGR_GetNumAIPlayersWithRoleOnTeam(BotTeamNumber, BOT_ROLE_COMMAND, pBot) > 0) { return false; } + + float ThisBotDist = vDist2DSq(pBot->Edict->v.origin, AITAC_GetCommChairLocation(BotTeamNumber)); + + // Only go commander if we're the closest bot to the chair + vector BotList = AIMGR_GetAIPlayersOnTeam(BotTeamNumber); + + for (auto it = BotList.begin(); it != BotList.end(); it++) + { + AvHAIPlayer* OtherBot = (*it); + + float OtherBotDist = vDist2DSq(OtherBot->Edict->v.origin, AITAC_GetCommChairLocation(BotTeamNumber)); + + if (OtherBot != pBot && IsPlayerActiveInGame(pBot->Edict) && OtherBotDist < ThisBotDist) + { + // We aren't the closest, let the other guy take command + return false; + } + } + + // We must be the closest! + return true; +} + +void UpdateAIMarinePlayerNSRole(AvHAIPlayer* pBot) +{ + AvHTeamNumber BotTeamNumber = pBot->Player->GetTeam(); + + if (BotTeamNumber == TEAM_IND) + { + SetNewAIPlayerRole(pBot, BOT_ROLE_NONE); + + return; + } + + if (ShouldAIPlayerTakeCommand(pBot)) + { + // We're going to go commander! + SetNewAIPlayerRole(pBot, BOT_ROLE_COMMAND); + return; + } + + int NumSweeperBots = AIMGR_GetNumAIPlayersWithRoleOnTeam(BotTeamNumber, BOT_ROLE_SWEEPER, pBot); + + // Always have a sweeper + if (NumSweeperBots < 1) + { + SetNewAIPlayerRole(pBot, BOT_ROLE_SWEEPER); + return; + } + + // Always go bombardier if we have a grenade launcher + if (PlayerHasWeapon(pBot->Player, WEAPON_MARINE_GL)) + { + SetNewAIPlayerRole(pBot, BOT_ROLE_BOMBARDIER); + return; + } + + // If we own less than half the res nodes in the map, then we want 2 marines to cap them. Otherwise, have 1 + float ResNodeOwnership = AITAC_GetTeamResNodeOwnership(BotTeamNumber); + + int DesiredResCappers = (ResNodeOwnership < 0.5f) ? 2 : 1; + + int NumCappers = AIMGR_GetNumAIPlayersWithRoleOnTeam(BotTeamNumber, BOT_ROLE_FIND_RESOURCES, pBot); + + if (NumCappers < DesiredResCappers) + { + SetNewAIPlayerRole(pBot, BOT_ROLE_FIND_RESOURCES); + return; + } + + // Everyone else goes assault + SetNewAIPlayerRole(pBot, BOT_ROLE_ASSAULT); + +} + +void AIPlayerNSThink(AvHAIPlayer* pBot) +{ + AvHTeam* BotTeam = GetGameRules()->GetTeam(pBot->Player->GetTeam()); + + if (!BotTeam) { return; } + + if (BotTeam->GetTeamType() == AVH_CLASS_TYPE_MARINE) + { + AIPlayerNSMarineThink(pBot); + } + else + { + AIPlayerNSAlienThink(pBot); + } +} + +AvHAIPlayerTask* AIPlayerGetNextTask(AvHAIPlayer* pBot) +{ + + // Any orders issued by the commander take priority over everything else + if (pBot->CommanderTask.TaskType != TASK_NONE) + { + if (pBot->SecondaryBotTask.bTaskIsUrgent) + { + return &pBot->SecondaryBotTask; + } + else + { + return &pBot->CommanderTask; + } + } + + // Prioritise healing our friends (heal tasks are only valid if the target is close by anyway) + if (pBot->SecondaryBotTask.TaskType == TASK_HEAL) + { + return &pBot->SecondaryBotTask; + } + + if (AITASK_IsTaskUrgent(pBot, &pBot->WantsAndNeedsTask)) + { + return &pBot->WantsAndNeedsTask; + } + + if (AITASK_IsTaskUrgent(pBot, &pBot->PrimaryBotTask)) + { + return &pBot->PrimaryBotTask; + } + + if (AITASK_IsTaskUrgent(pBot, &pBot->SecondaryBotTask)) + { + return &pBot->SecondaryBotTask; + } + + if (pBot->WantsAndNeedsTask.TaskType != TASK_NONE) + { + return &pBot->WantsAndNeedsTask; + } + + if (pBot->SecondaryBotTask.TaskType != TASK_NONE) + { + return &pBot->SecondaryBotTask; + } + + return &pBot->PrimaryBotTask; +} + +void AIPlayerNSMarineThink(AvHAIPlayer* pBot) +{ + UpdateAIMarinePlayerNSRole(pBot); + + if (pBot->BotRole == BOT_ROLE_COMMAND) + { + AICOMM_CommanderThink(pBot); + return; + } + + if (!pBot->CurrentTask) { pBot->CurrentTask = &pBot->PrimaryBotTask; } + + if (gpGlobals->time < pBot->BotNextTaskEvaluationTime) + { + if (pBot->CurrentTask && pBot->CurrentTask->TaskType != TASK_NONE) + { + BotProgressTask(pBot, pBot->CurrentTask); + return; + } + } + + pBot->BotNextTaskEvaluationTime = gpGlobals->time + frandrange(0.2f, 0.5f); + + AITASK_BotUpdateAndClearTasks(pBot); + + AIPlayerSetPrimaryMarineTask(pBot, &pBot->PrimaryBotTask); + AIPlayerSetSecondaryMarineTask(pBot, &pBot->SecondaryBotTask); + + pBot->CurrentTask = AIPlayerGetNextTask(pBot); + + if (pBot->CurrentTask && pBot->CurrentTask->TaskType != TASK_NONE) + { + BotProgressTask(pBot, pBot->CurrentTask); + } + + if (pBot->DesiredCombatWeapon == WEAPON_NONE) + { + pBot->DesiredCombatWeapon = BotMarineChooseBestWeapon(pBot, nullptr); + } + + if (pBot->CommanderTask.TaskType != TASK_NONE) + { + UTIL_DrawLine(INDEXENT(1), pBot->Edict->v.origin, pBot->CommanderTask.TaskLocation); + } +} + +void AIPlayerSetPrimaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) +{ + switch (pBot->BotRole) + { + case BOT_ROLE_SWEEPER: + AIPlayerSetMarineSweeperPrimaryTask(pBot, Task); + return; + case BOT_ROLE_FIND_RESOURCES: + AIPlayerSetMarineCapperPrimaryTask(pBot, Task); + return; + case BOT_ROLE_ASSAULT: + AIPlayerSetMarineAssaultPrimaryTask(pBot, Task); + return; + case BOT_ROLE_BOMBARDIER: + AIPlayerSetMarineBombardierPrimaryTask(pBot, Task); + return; + default: + return; + } + +} + +void AIPlayerSetMarineSweeperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) +{ + if (Task->TaskType == TASK_GUARD) { return; } + + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + + Vector CommChairLocation = AITAC_GetCommChairLocation(BotTeam); + + DeployableSearchFilter StructureFilter; + StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; + StructureFilter.DeployableTeam = BotTeam; + StructureFilter.ReachabilityTeam = BotTeam; + StructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + StructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + + if (AITAC_GetNumDeployablesNearLocation(CommChairLocation, &StructureFilter) < 2) + { + Task->TaskType = TASK_GUARD; + Task->TaskLocation = UTIL_GetRandomPointOnNavmeshInRadius(pBot->BotNavInfo.NavProfile, CommChairLocation, UTIL_MetresToGoldSrcUnits(10.0f)); + Task->bTaskIsUrgent = false; + Task->TaskLength = frandrange(20.0f, 30.0f); + return; + } + + AvHAIBuildableStructure* NearestPG = AITAC_FindClosestDeployableToLocation(pBot->Edict->v.origin, &StructureFilter); + + vector AllPG = AITAC_FindAllDeployables(pBot->Edict->v.origin, &StructureFilter); + + AvHAIBuildableStructure* RandomPG = nullptr; + int HighestRand = 0; + + for (auto it = AllPG.begin(); it != AllPG.end(); it++) + { + AvHAIBuildableStructure* ThisStruct = (*it); + + if (ThisStruct == NearestPG) { continue; } + + int ThisRand = irandrange(0, 100); + + if (!RandomPG || ThisRand > HighestRand) + { + RandomPG = ThisStruct; + HighestRand = ThisRand; + } + } + + if (RandomPG) + { + Task->TaskType = TASK_GUARD; + Task->TaskLocation = UTIL_GetRandomPointOnNavmeshInRadius(pBot->BotNavInfo.NavProfile, RandomPG->Location, UTIL_MetresToGoldSrcUnits(5.0f)); + Task->bTaskIsUrgent = false; + Task->TaskLength = frandrange(20.0f, 30.0f); + return; + } + +} + +void AIPlayerSetMarineCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) +{ + DeployableSearchFilter NodeFilter; + NodeFilter.DeployableTeam = TEAM_IND; + NodeFilter.ReachabilityTeam = pBot->Player->GetTeam(); + NodeFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + + AvHAIResourceNode* NearestNode = nullptr; + float MinDist = 0.0f; + + vector UnclaimedResourceNodes = AITAC_GetAllMatchingResourceNodes(pBot->Edict->v.origin, &NodeFilter); + + for (auto it = UnclaimedResourceNodes.begin(); it != UnclaimedResourceNodes.end(); it++) + { + AvHAIResourceNode* ResNode = (*it); + int NumCappers = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ResNode->Location, UTIL_MetresToGoldSrcUnits(4.0), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + + // Only want one capper to grab an empty one + if (NumCappers == 0) + { + float ThisDist = vDist2DSq(pBot->Edict->v.origin, ResNode->Location); + + if (!NearestNode || ThisDist < MinDist) + { + NearestNode = ResNode; + MinDist = ThisDist; + } + } + } + + if (NearestNode) + { + AITASK_SetCapResNodeTask(pBot, Task, NearestNode, false); + return; + } + + MinDist = 0.0f; + + NodeFilter.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam()); + + vector EnemyResourceNodes = AITAC_GetAllMatchingResourceNodes(pBot->Edict->v.origin, &NodeFilter); + + for (auto it = EnemyResourceNodes.begin(); it != EnemyResourceNodes.end(); it++) + { + AvHAIResourceNode* ResNode = (*it); + int NumCappers = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ResNode->Location, UTIL_MetresToGoldSrcUnits(4.0), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + + // Allow for 2 cappers to attack an enemy resource node + if (NumCappers < 2) + { + float ThisDist = vDist2DSq(pBot->Edict->v.origin, ResNode->Location); + + if (!NearestNode || ThisDist < MinDist) + { + NearestNode = ResNode; + MinDist = ThisDist; + } + } + } + + if (NearestNode) + { + AITASK_SetCapResNodeTask(pBot, Task, NearestNode, false); + return; + } + + // No res nodes to cap, go do assault stuff + AIPlayerSetMarineAssaultPrimaryTask(pBot, Task); +} + +void AIPlayerSetMarineAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) +{ + // Go attack sieged hive + const AvHAIHiveDefinition* ActiveSiegeHive = AITAC_GetNearestHiveUnderActiveSiege(pBot->Player->GetTeam(), pBot->Edict->v.origin); + + if (ActiveSiegeHive) + { + AITASK_SetAttackTask(pBot, Task, ActiveSiegeHive->HiveEntity->edict(), false); + return; + } + + // Go to empty hive without other marines in it + + vector AllHives = AITAC_GetAllHives(); + + AvHAIHiveDefinition* NearestEmptyHive = nullptr; + float MinDist = 0.0f; + + for (auto it = AllHives.begin(); it != AllHives.end(); it++) + { + AvHAIHiveDefinition* ThisHive = (*it); + if (ThisHive->Status != HIVE_STATUS_UNBUILT) { continue; } + + int NumMarinesSecuring = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ThisHive->Location, UTIL_MetresToGoldSrcUnits(15.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + + if (NumMarinesSecuring < 2) + { + float ThisDist = vDist2DSq(ThisHive->Location, pBot->Edict->v.origin); + + if (!NearestEmptyHive || ThisDist < MinDist) + { + NearestEmptyHive = ThisHive; + MinDist = ThisDist; + } + } + } + + if (NearestEmptyHive) + { + AITASK_SetSecureHiveTask(pBot, Task, NearestEmptyHive->HiveEntity->edict(), NearestEmptyHive->FloorLocation, false); + return; + } + + // Go to a good siege location if phase gates available + + if (AITAC_PhaseGatesAvailable(pBot->Player->GetTeam())) + { + const AvHAIHiveDefinition* ActiveHive = AITAC_GetActiveHiveNearestLocation(pBot->Edict->v.origin); + + if (ActiveHive) + { + if (Task->TaskType != TASK_MOVE) + { + AITASK_SetMoveTask(pBot, Task, UTIL_GetRandomPointOnNavmeshInDonut(pBot->BotNavInfo.NavProfile, ActiveHive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), UTIL_MetresToGoldSrcUnits(20.0f)), false); + } + + return; + + } + } + +} + +void AIPlayerSetMarineBombardierPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) +{ + // Go attack sieged hive + + // Go clear res nodes + + +} + +void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) +{ + // Find any nearby unbuilt structures + DeployableSearchFilter UnbuiltFilter; + UnbuiltFilter.DeployableTypes = SEARCH_ALL_MARINE_STRUCTURES; + UnbuiltFilter.DeployableTeam = pBot->Player->GetTeam(); + UnbuiltFilter.ReachabilityTeam = pBot->Player->GetTeam(); + UnbuiltFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + UnbuiltFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_COMPLETED; + UnbuiltFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); + + vector BuildableStructures = AITAC_FindAllDeployables(pBot->Edict->v.origin, &UnbuiltFilter); + + AvHAIBuildableStructure* NearestStructure = nullptr; + float MinDist = 0.0f; + + 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); + + 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); + MinDist = ThisDist; + } + } + } + + if (NearestStructure) + { + AITASK_SetBuildTask(pBot, Task, NearestStructure->edict, false); + return; + } + +} + +void AIPlayerNSAlienThink(AvHAIPlayer* pBot) +{ + UpdateAIAlienPlayerNSRole(pBot); +} + +void AIPlayerCOThink(AvHAIPlayer* pBot) +{ + +} + +void AIPlayerDMThink(AvHAIPlayer* pBot) +{ + +} + +void AIPlayerThink(AvHAIPlayer* pBot) +{ + switch (GetGameRules()->GetMapMode()) + { + case MAP_MODE_NS: + AIPlayerNSThink(pBot); + break; + case MAP_MODE_CO: + AIPlayerCOThink(pBot); + break; + default: + AIPlayerDMThink(pBot); + break; + } + + AvHAIWeapon DesiredWeapon = (pBot->DesiredMoveWeapon != WEAPON_NONE) ? pBot->DesiredMoveWeapon : pBot->DesiredCombatWeapon; + + if (DesiredWeapon != WEAPON_NONE && GetPlayerCurrentWeapon(pBot->Player) != DesiredWeapon) + { + BotSwitchToWeapon(pBot, DesiredWeapon); + } +} + void TestNavThink(AvHAIPlayer* pBot) { AITASK_BotUpdateAndClearTasks(pBot); @@ -1579,10 +2121,10 @@ void UpdateCommanderOrders(AvHAIPlayer* pBot) switch (it->GetOrderType()) { case ORDERTYPEL_MOVE: - AITASK_SetMoveTask(pBot, &pBot->CommanderTask, OrderLocation, true); + AIPlayerReceiveMoveOrder(pBot, OrderLocation); break; case ORDERTYPET_BUILD: - AITASK_SetBuildTask(pBot, &pBot->CommanderTask, INDEXENT(it->GetTargetIndex()), true); + AIPlayerReceiveBuildOrder(pBot, INDEXENT(it->GetTargetIndex())); break; default: break; @@ -1591,6 +2133,47 @@ void UpdateCommanderOrders(AvHAIPlayer* pBot) } } +void AIPlayerReceiveBuildOrder(AvHAIPlayer* pBot, edict_t* BuildTarget) +{ + AITASK_SetBuildTask(pBot, &pBot->CommanderTask, BuildTarget, true); +} + +void AIPlayerReceiveMoveOrder(AvHAIPlayer* pBot, Vector Destination) +{ + + const AvHAIResourceNode* ResNodeRef = AITAC_GetNearestResourceNodeToLocation(Destination); + + // We've been asked to go to a resource node if the movement order is near it + if (ResNodeRef && vDist2DSq(ResNodeRef->Location, Destination) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) + { + // If this resource node doesn't belong to us, or the tower isn't fully built, interpret the order as a "cap this node" order + if (ResNodeRef->OwningTeam != pBot->Player->GetTeam() || FNullEnt(ResNodeRef->ActiveTowerEntity) || !UTIL_StructureIsFullyBuilt(ResNodeRef->ActiveTowerEntity)) + { + AITASK_SetCapResNodeTask(pBot, &pBot->CommanderTask, ResNodeRef, false); + pBot->CommanderTask.bIssuedByCommander = true; + return; + } + } + + const AvHAIHiveDefinition* HiveRef = AITAC_GetHiveNearestLocation(Destination); + + // Have we been asked to go to an empty hive? If so, then treat the order as a "help secure this hive" command + if (HiveRef && HiveRef->Status == HIVE_STATUS_UNBUILT && vDist2DSq(HiveRef->Location, Destination) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) + { + if (!AICOMM_IsHiveFullySecured(pBot, HiveRef, false)) + { + AITASK_SetSecureHiveTask(pBot, &pBot->CommanderTask, HiveRef->HiveEntity->edict(), Destination, false); + pBot->CommanderTask.bIssuedByCommander = true; + return; + } + } + + // Otherwise, treat as a normal move order. Go there and wait a bit to see what the commander wants to do next + AITASK_SetMoveTask(pBot, &pBot->CommanderTask, Destination, true); + pBot->CommanderTask.bIssuedByCommander = true; + +} + void BotStopCommanderMode(AvHAIPlayer* pBot) { // Thanks EterniumDev (Alien) for logic to allow commander AI to leave the chair and build structures when needed diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.h b/main/source/mod/AIPlayers/AvHAIPlayer.h index 67d2b9bd..9c673e04 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.h +++ b/main/source/mod/AIPlayers/AvHAIPlayer.h @@ -53,10 +53,29 @@ void UpdateBotChat(AvHAIPlayer* pBot); void ClearBotInputs(AvHAIPlayer* pBot); void StartNewBotFrame(AvHAIPlayer* pBot); +void AIPlayerThink(AvHAIPlayer* pBot); +// Think routine for regular NS game mode +void AIPlayerNSThink(AvHAIPlayer* pBot); +void AIPlayerNSMarineThink(AvHAIPlayer* pBot); +void AIPlayerNSAlienThink(AvHAIPlayer* pBot); +// Think routine for the combat game mode +void AIPlayerCOThink(AvHAIPlayer* pBot); +// Think routine for the deathmatch game mode (e.g. when playing CS maps) +void AIPlayerDMThink(AvHAIPlayer* pBot); + void TestNavThink(AvHAIPlayer* pBot); void DroneThink(AvHAIPlayer* pBot); void CustomThink(AvHAIPlayer* pBot); +AvHAIPlayerTask* AIPlayerGetNextTask(AvHAIPlayer* pBot); +void AIPlayerSetPrimaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); +void AIPlayerSetMarineSweeperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); +void AIPlayerSetMarineCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); +void AIPlayerSetMarineAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); +void AIPlayerSetMarineBombardierPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); + +void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); + void BotSwitchToWeapon(AvHAIPlayer* pBot, AvHAIWeapon NewWeaponSlot); bool ShouldBotThink(AvHAIPlayer* pBot); @@ -64,7 +83,17 @@ bool ShouldBotThink(AvHAIPlayer* pBot); void BotResumePlay(AvHAIPlayer* pBot); void UpdateCommanderOrders(AvHAIPlayer* pBot); +void AIPlayerReceiveMoveOrder(AvHAIPlayer* pBot, Vector Destination); +void AIPlayerReceiveBuildOrder(AvHAIPlayer* pBot, edict_t* BuildTarget); void BotStopCommanderMode(AvHAIPlayer* pBot); +void SetNewAIPlayerRole(AvHAIPlayer* pBot, AvHAIBotRole NewRole); +void UpdateAIMarinePlayerNSRole(AvHAIPlayer* pBot); +void UpdateAIAlienPlayerNSRole(AvHAIPlayer* pBot); +void UpdateAIPlayerCORole(AvHAIPlayer* pBot); +void UpdateAIPlayerDMRole(AvHAIPlayer* pBot); + +bool ShouldAIPlayerTakeCommand(AvHAIPlayer* pBot); + #endif \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp index 10422be0..4e36c1c3 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp @@ -68,6 +68,21 @@ string BotNames[MAX_PLAYERS] = { "MrRobot", "TerminalFerocity" }; +AvHAICommanderMode AIMGR_GetCommanderMode() +{ + if (avh_botcommandermode.value == 0) + { + return COMMANDERMODE_DISABLED; + } + + if (avh_botcommandermode.value == 1) + { + return COMMANDERMODE_IFNOHUMAN; + } + + return COMMANDERMODE_ENABLED; + +} void AIMGR_UpdateAIPlayerCounts() { @@ -472,7 +487,10 @@ byte BotThrottledMsec(AvHAIPlayer* inAIPlayer) if (newmsec > 255) { newmsec = 255; - } + } + + // save the command time + inAIPlayer->f_previous_command_time = gpGlobals->time; return (byte)newmsec; } @@ -547,17 +565,10 @@ void AIMGR_UpdateAIPlayers() UpdateBotChat(bot); - CustomThink(bot); + AIPlayerThink(bot); AIDEBUG_DrawPath(DebugPath, 0.0f); - AvHAIWeapon DesiredWeapon = (bot->DesiredMoveWeapon != WEAPON_NONE) ? bot->DesiredMoveWeapon : bot->DesiredCombatWeapon; - - if (DesiredWeapon != WEAPON_NONE && GetPlayerCurrentWeapon(bot->Player) != DesiredWeapon) - { - BotSwitchToWeapon(bot, DesiredWeapon); - } - BotUpdateDesiredViewRotation(bot); } else @@ -567,10 +578,7 @@ void AIMGR_UpdateAIPlayers() } // Needed to correctly handle client prediction and physics calculations - byte adjustedmsec = BotThrottledMsec(bot); - - // save the command time - bot->f_previous_command_time = gpGlobals->time; + byte adjustedmsec = BotThrottledMsec(bot); // Simulate PM_PlayerMove so client prediction and stuff can be executed correctly. RUN_AI_MOVE(bot->Edict, bot->Edict->v.v_angle, bot->ForwardMove, @@ -596,6 +604,29 @@ int AIMGR_GetNumAIPlayers() return ActiveAIPlayers.size(); } +vector AIMGR_GetAllPlayersOnTeam(AvHTeamNumber Team) +{ + vector Result; + + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + edict_t* PlayerEdict = INDEXENT(i); + + if (!FNullEnt(PlayerEdict) && PlayerEdict->v.team == Team) + { + AvHPlayer* PlayerRef = dynamic_cast(CBaseEntity::Instance(PlayerEdict)); + + if (PlayerRef) + { + Result.push_back(PlayerRef); + } + + } + } + + return Result; +} + int AIMGR_GetNumAIPlayersOnTeam(AvHTeamNumber Team) { int Result = 0; @@ -611,6 +642,46 @@ int AIMGR_GetNumAIPlayersOnTeam(AvHTeamNumber Team) return Result; } +int AIMGR_GetNumHumanPlayersOnTeam(AvHTeamNumber Team) +{ + int Result = 0; + + vector TeamPlayers = AIMGR_GetAllPlayersOnTeam(Team); + + for (auto it = TeamPlayers.begin(); it != TeamPlayers.end(); it++) + { + AvHPlayer* ThisPlayer = (*it); + edict_t* PlayerEdict = ThisPlayer->edict(); + + if (!(PlayerEdict->v.flags & FL_FAKECLIENT)) + { + Result++; + } + } + + return Result; +} + +int AIMGR_GetNumAIPlayersWithRoleOnTeam(AvHTeamNumber Team, AvHAIBotRole Role, AvHAIPlayer* IgnoreAIPlayer) +{ + int Result = 0; + + for (auto it = ActiveAIPlayers.begin(); it != ActiveAIPlayers.end(); it++) + { + if (&(*it) == IgnoreAIPlayer) { continue; } + + if (it->Player->GetTeam() == Team) + { + if (it->BotRole == Role) + { + Result++; + } + } + } + + return Result; +} + int AIMGR_AIPlayerExistsOnTeam(AvHTeamNumber Team) { for (auto it = ActiveAIPlayers.begin(); it != ActiveAIPlayers.end(); it++) @@ -662,11 +733,15 @@ void AIMGR_ResetRound() void AIMGR_RoundStarted() { + AITAC_PopulateResourceNodes(); + AITAC_PopulateHiveData(); + + AITAC_RefreshResourceNodes(); + AITAC_RefreshHiveData(); UTIL_UpdateTileCache(); - - AITAC_RefreshResourceNodes(); + } void AIMGR_ClearBotData() diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.h b/main/source/mod/AIPlayers/AvHAIPlayerManager.h index 41455776..ee05c64d 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.h +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.h @@ -36,6 +36,8 @@ void AIMGR_UpdateTeamBalance(); // Called by UpdateAIPlayerCounts. If auto-mode is fill teams, will add/remove bots needed to maintain minimum player counts and balance void AIMGR_UpdateFillTeams(); +vector AIMGR_GetAllPlayersOnTeam(AvHTeamNumber Team); + // How many AI players are in the game (does not include third-party bots like RCBot/Whichbot) int AIMGR_GetNumAIPlayers(); // Returns true if an AI player is on the requested team (does NOT include third-party bots like RCBot/Whichbot) @@ -43,11 +45,16 @@ int AIMGR_AIPlayerExistsOnTeam(AvHTeamNumber Team); void AIMGR_UpdateAIMapData(); +AvHAICommanderMode AIMGR_GetCommanderMode(); + void AIDEBUG_SetDebugVector1(const Vector NewVector); void AIDEBUG_SetDebugVector2(const Vector NewVector); void AIDEBUG_TestPathFind(); int AIMGR_GetNumAIPlayersOnTeam(AvHTeamNumber Team); +int AIMGR_GetNumHumanPlayersOnTeam(AvHTeamNumber Team); + +int AIMGR_GetNumAIPlayersWithRoleOnTeam(AvHTeamNumber Team, AvHAIBotRole Role, AvHAIPlayer* IgnoreAIPlayer); AvHAIPlayer* AIMGR_GetAICommander(AvHTeamNumber Team); diff --git a/main/source/mod/AIPlayers/AvHAITactical.cpp b/main/source/mod/AIPlayers/AvHAITactical.cpp index 82132dfd..0358549f 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.cpp +++ b/main/source/mod/AIPlayers/AvHAITactical.cpp @@ -653,22 +653,28 @@ Vector AITAC_GetFloorLocationForHive(const AvHAIHiveDefinition* Hive) } } +void AITAC_PopulateHiveData() +{ + Hives.clear(); + + FOR_ALL_ENTITIES(kesTeamHive, AvHHive*) + + AvHAIHiveDefinition NewHive; + NewHive.HiveEntity = theEntity; + NewHive.Location = theEntity->pev->origin; + NewHive.HiveResNodeRef = AITAC_GetNearestResourceNodeToLocation(theEntity->pev->origin); + NewHive.FloorLocation = UTIL_GetFloorUnderEntity(theEntity->edict()); // Some hives are suspended in the air, this is the floor location directly beneath it + + Hives.push_back(NewHive); + + END_FOR_ALL_ENTITIES(kesTeamHive) +} + void AITAC_RefreshHiveData() { if (Hives.size() == 0) { - FOR_ALL_ENTITIES(kesTeamHive, AvHHive*) - - AvHAIHiveDefinition NewHive; - NewHive.HiveEntity = theEntity; - NewHive.Location = theEntity->pev->origin; - NewHive.HiveResNodeRef = AITAC_GetNearestResourceNodeToLocation(theEntity->pev->origin); - NewHive.FloorLocation = UTIL_GetFloorUnderEntity(theEntity->edict()); // Some hives are suspended in the air, this is the floor location directly beneath it - - Hives.push_back(NewHive); - - END_FOR_ALL_ENTITIES(kesTeamHive) - + AITAC_PopulateHiveData(); } int NextRefresh = 0; @@ -1024,23 +1030,30 @@ void AITAC_RefreshReachabilityForResNode(AvHAIResourceNode* ResNode) } +void AITAC_PopulateResourceNodes() +{ + ResourceNodes.clear(); + + FOR_ALL_ENTITIES(kesFuncResource, AvHFuncResource*) + + AvHAIResourceNode NewResNode; + NewResNode.ResourceEntity = theEntity; + NewResNode.Location = theEntity->pev->origin; + NewResNode.TeamAReachabilityFlags = AI_REACHABILITY_NONE; + NewResNode.TeamBReachabilityFlags = AI_REACHABILITY_NONE; + NewResNode.bReachabilityMarkedDirty = true; + NewResNode.NextReachabilityRefreshTime = 0.0f; + + ResourceNodes.push_back(NewResNode); + + END_FOR_ALL_ENTITIES(kesFuncResource) +} + void AITAC_RefreshResourceNodes() { if (ResourceNodes.size() == 0) { - FOR_ALL_ENTITIES(kesFuncResource, AvHFuncResource*) - - AvHAIResourceNode NewResNode; - NewResNode.ResourceEntity = theEntity; - NewResNode.Location = theEntity->pev->origin; - NewResNode.TeamAReachabilityFlags = AI_REACHABILITY_NONE; - NewResNode.TeamBReachabilityFlags = AI_REACHABILITY_NONE; - NewResNode.bReachabilityMarkedDirty = true; - NewResNode.NextReachabilityRefreshTime = 0.0f; - - ResourceNodes.push_back(NewResNode); - - END_FOR_ALL_ENTITIES(kesFuncResource) + AITAC_PopulateResourceNodes(); } for (auto it = ResourceNodes.begin(); it != ResourceNodes.end(); it++) @@ -2259,6 +2272,19 @@ AvHAIHiveDefinition* AITAC_GetHiveFromEdict(const edict_t* Edict) return nullptr; } +AvHAIResourceNode* AITAC_GetResourceNodeFromEdict(const edict_t* Edict) +{ + for (auto it = ResourceNodes.begin(); it != ResourceNodes.end(); it++) + { + if (it->ResourceEntity->edict() == Edict) + { + return &(*it); + } + } + + return nullptr; +} + const AvHAIHiveDefinition* AITAC_GetHiveNearestLocation(const Vector SearchLocation) { AvHAIHiveDefinition* Result = nullptr; @@ -2339,6 +2365,116 @@ AvHAIResourceNode* AITAC_GetNearestResourceNodeToLocation(const Vector Location) return Result; } +float AITAC_GetTeamResNodeOwnership(const AvHTeamNumber Team) +{ + int NumViableResNodes = 0; + int NumOwnedResNodes = 0; + + for (auto it = ResourceNodes.begin(); it != ResourceNodes.end(); it++) + { + unsigned int CheckReachabilityFlags = (it->TeamAReachabilityFlags | it->TeamBReachabilityFlags); + + if (Team != TEAM_IND) + { + CheckReachabilityFlags = (Team == GetGameRules()->GetTeamANumber()) ? it->TeamAReachabilityFlags : it->TeamBReachabilityFlags; + } + + if (CheckReachabilityFlags == AI_REACHABILITY_UNREACHABLE) { continue; } + + NumViableResNodes++; + + if (it->OwningTeam == Team) + { + NumOwnedResNodes++; + } + } + + // If there are no viable resource nodes, then report we own them all to avoid divide by zero + if (NumViableResNodes == 0) { return 1.0f; } + + return (float)NumOwnedResNodes / (float)NumViableResNodes; +} + +int AITAC_GetNumResourceNodesNearLocation(const Vector Location, const DeployableSearchFilter* Filter) +{ + int Result = 0; + + float MinDistSq = sqrf(Filter->MinSearchRadius); + float MaxDistSq = sqrf(Filter->MaxSearchRadius); + + bool bUseMinDist = MinDistSq > 0.1f; + bool bUseMaxDist = MaxDistSq > 0.1f; + + float CurrMinDist = 0; + + for (auto it = ResourceNodes.begin(); it != ResourceNodes.end(); it++) + { + if (Filter->ReachabilityFlags != AI_REACHABILITY_NONE) + { + unsigned int CheckReachabilityFlags = (it->TeamAReachabilityFlags | it->TeamBReachabilityFlags); + + if (Filter->ReachabilityTeam != TEAM_IND) + { + CheckReachabilityFlags = (Filter->ReachabilityTeam == GetGameRules()->GetTeamANumber()) ? it->TeamAReachabilityFlags : it->TeamBReachabilityFlags; + } + + if (!(CheckReachabilityFlags & Filter->ReachabilityFlags)) { continue; } + } + + + if (it->OwningTeam != Filter->DeployableTeam) { continue; } + + float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it->Location, Location)) : vDist2DSq(it->Location, Location); + + if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq) && (!Result || DistSq < CurrMinDist)) + { + Result++; + } + } + + return Result; +} + +vector AITAC_GetAllMatchingResourceNodes(const Vector Location, const DeployableSearchFilter* Filter) +{ + vector Results; + + float MinDistSq = sqrf(Filter->MinSearchRadius); + float MaxDistSq = sqrf(Filter->MaxSearchRadius); + + bool bUseMinDist = MinDistSq > 0.1f; + bool bUseMaxDist = MaxDistSq > 0.1f; + + float CurrMinDist = 0; + + for (auto it = ResourceNodes.begin(); it != ResourceNodes.end(); it++) + { + if (Filter->ReachabilityFlags != AI_REACHABILITY_NONE) + { + unsigned int CheckReachabilityFlags = (it->TeamAReachabilityFlags | it->TeamBReachabilityFlags); + + if (Filter->ReachabilityTeam != TEAM_IND) + { + CheckReachabilityFlags = (Filter->ReachabilityTeam == GetGameRules()->GetTeamANumber()) ? it->TeamAReachabilityFlags : it->TeamBReachabilityFlags; + } + + if (!(CheckReachabilityFlags & Filter->ReachabilityFlags)) { continue; } + } + + + if (it->OwningTeam != Filter->DeployableTeam) { continue; } + + float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it->Location, Location)) : vDist2DSq(it->Location, Location); + + if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq)) + { + Results.push_back(&(*it)); + } + } + + return Results; +} + AvHAIResourceNode* AITAC_FindNearestResourceNodeToLocation(const Vector Location, const DeployableSearchFilter* Filter) { AvHAIResourceNode* Result = nullptr; @@ -2380,6 +2516,22 @@ AvHAIResourceNode* AITAC_FindNearestResourceNodeToLocation(const Vector Location } +int AITAC_GetNumActivePlayersOnTeam(const AvHTeamNumber Team) +{ + int Result = 0; + + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + edict_t* PlayerEdict = INDEXENT(i); + + if (!FNullEnt(PlayerEdict) && !PlayerEdict->free && IsPlayerActiveInGame(PlayerEdict)) { Result++; } + + + } + + return Result; +} + int AITAC_GetNumPlayersOfTeamInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 IgnoreClass) { int Result = 0; @@ -2996,7 +3148,7 @@ const AvHAIHiveDefinition* AITAC_GetNearestHiveUnderActiveSiege(AvHTeamNumber Si DeployableSearchFilter SiegeFilter; SiegeFilter.DeployableTypes = STRUCTURE_MARINE_ADVTURRETFACTORY; SiegeFilter.DeployableTeam = SiegingTeam; - SiegeFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); + SiegeFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(25.0f); SiegeFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; SiegeFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; @@ -3031,7 +3183,7 @@ edict_t* AITAC_GetMarineEligibleToBuildSiege(AvHTeamNumber Team, const AvHAIHive edict_t* Result = nullptr; - vector TeamPlayers = AITAC_GetAllPlayersOnTeam(Team); + vector TeamPlayers = AIMGR_GetAllPlayersOnTeam(Team); float MinDist = 0.0f; @@ -3062,7 +3214,7 @@ edict_t* AITAC_GetNearestHiddenPlayerInLocation(AvHTeamNumber Team, const Vector edict_t* Result = nullptr; float MaxRadiusSq = sqrf(MaxRadius); - vector TeamPlayers = AITAC_GetAllPlayersOnTeam(Team); + vector TeamPlayers = AIMGR_GetAllPlayersOnTeam(Team); float MinDist = 0.0f; @@ -3088,28 +3240,7 @@ edict_t* AITAC_GetNearestHiddenPlayerInLocation(AvHTeamNumber Team, const Vector return Result; } -vector AITAC_GetAllPlayersOnTeam(AvHTeamNumber Team) -{ - vector Result; - for (int i = 1; i <= gpGlobals->maxClients; i++) - { - edict_t* PlayerEdict = INDEXENT(i); - - if (!FNullEnt(PlayerEdict) && PlayerEdict->v.team == Team) - { - AvHPlayer* PlayerRef = dynamic_cast(CBaseEntity::Instance(PlayerEdict)); - - if (PlayerRef) - { - Result.push_back(PlayerRef); - } - - } - } - - return Result; -} const vector AITAC_GetAllResourceNodes() { @@ -3139,7 +3270,7 @@ bool AITAC_AnyPlayerOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, fl { float distSq = sqrf(SearchRadius); - vector Players = AITAC_GetAllPlayersOnTeam(Team); + vector Players = AIMGR_GetAllPlayersOnTeam(Team); for (auto it = Players.begin(); it != Players.end(); it++) { diff --git a/main/source/mod/AIPlayers/AvHAITactical.h b/main/source/mod/AIPlayers/AvHAITactical.h index b4c321c7..c70ad8cf 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.h +++ b/main/source/mod/AIPlayers/AvHAITactical.h @@ -27,7 +27,9 @@ AvHAIBuildableStructure* AITAC_FindFurthestDeployableFromLocation(const Vector& AvHAIBuildableStructure* AITAC_GetDeployableRefFromEdict(const edict_t* Structure); AvHAIBuildableStructure* AITAC_GetNearestDeployableDirectlyReachable(AvHAIPlayer* pBot, const Vector Location, const DeployableSearchFilter* Filter); int AITAC_GetNumDeployablesNearLocation(const Vector& Location, const DeployableSearchFilter* Filter); +void AITAC_PopulateHiveData(); void AITAC_RefreshHiveData(); +void AITAC_PopulateResourceNodes(); void AITAC_RefreshResourceNodes(); void AITAC_UpdateMapAIData(); void AITAC_CheckNavMeshModified(); @@ -102,14 +104,20 @@ bool UTIL_StructureIsRecycling(edict_t* Structure); bool AITAC_StructureCanBeUpgraded(edict_t* Structure); AvHAIHiveDefinition* AITAC_GetHiveFromEdict(const edict_t* Edict); +AvHAIResourceNode* AITAC_GetResourceNodeFromEdict(const edict_t* Edict); +// What percentage of all viable (can be reached by the requested team) resource nodes does the team currently own? Expressed as 0.0 - 1.0 +float AITAC_GetTeamResNodeOwnership(const AvHTeamNumber Team); +int AITAC_GetNumResourceNodesNearLocation(const Vector Location, const DeployableSearchFilter* Filter); AvHAIResourceNode* AITAC_FindNearestResourceNodeToLocation(const Vector Location, const DeployableSearchFilter* Filter); AvHAIResourceNode* AITAC_GetNearestResourceNodeToLocation(const Vector Location); +vector AITAC_GetAllMatchingResourceNodes(const Vector Location, const DeployableSearchFilter* Filter); bool UTIL_IsBuildableStructureStillReachable(AvHAIPlayer* pBot, const edict_t* Structure); bool UTIL_IsDroppedItemStillReachable(AvHAIPlayer* pBot, const edict_t* Item); AvHAIWeapon UTIL_GetWeaponTypeFromEdict(const edict_t* ItemEdict); +int AITAC_GetNumActivePlayersOnTeam(const AvHTeamNumber Team); int AITAC_GetNumPlayersOfTeamInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 IgnoreClass); int AITAC_GetNumPlayersOnTeamOfClass(const AvHTeamNumber Team, const AvHUser3 SearchClass, const edict_t* IgnorePlayer); edict_t* AITAC_GetNearestPlayerOfClassInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 SearchClass); @@ -143,7 +151,6 @@ int AITAC_GetNumDeadPlayersOnTeam(const AvHTeamNumber Team); const AvHAIHiveDefinition* AITAC_GetNearestHiveUnderActiveSiege(AvHTeamNumber SiegingTeam, const Vector SearchLocation); edict_t* AITAC_GetMarineEligibleToBuildSiege(AvHTeamNumber Team, const AvHAIHiveDefinition* Hive); -vector AITAC_GetAllPlayersOnTeam(AvHTeamNumber Team); edict_t* AITAC_GetNearestHiddenPlayerInLocation(AvHTeamNumber Team, const Vector Location, const float MaxRadius); const vector AITAC_GetAllResourceNodes(); diff --git a/main/source/mod/AIPlayers/AvHAITask.cpp b/main/source/mod/AIPlayers/AvHAITask.cpp index 2cbd44e2..5ed1710b 100644 --- a/main/source/mod/AIPlayers/AvHAITask.cpp +++ b/main/source/mod/AIPlayers/AvHAITask.cpp @@ -332,6 +332,17 @@ bool AITASK_IsTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) } case TASK_REINFORCE_STRUCTURE: return AITASK_IsReinforceStructureTaskStillValid(pBot, Task); + case TASK_SECURE_HIVE: + { + if (IsPlayerMarine(pBot->Edict)) + { + return AITASK_IsMarineSecureHiveTaskStillValid(pBot, Task); + } + else + { + return false; + } + } case TASK_DEFEND: return AITASK_IsDefendTaskStillValid(pBot, Task); case TASK_WELD: @@ -548,11 +559,15 @@ bool AITASK_IsMarineBuildTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task return false; } + // Always go build if commanded to, regardless of how many are already working on it if (!Task->bIssuedByCommander) { int NumBuilders = AITAC_GetNumPlayersOfTeamInArea((AvHTeamNumber)pBot->Edict->v.team, Task->TaskTarget->v.origin, UTIL_MetresToGoldSrcUnits(2.0f), false, pBot->Edict, AVH_USER3_NONE); + + // Only one marine should build stuff if it's near the marine base. If not, then two for safety + int NumDesiredBuilders = (vDist2DSq(Task->TaskTarget->v.origin, AITAC_GetCommChairLocation(pBot->Player->GetTeam())) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) ? 1 : 2; - if (NumBuilders >= 2) + if (NumBuilders >= NumDesiredBuilders) { return false; } @@ -764,20 +779,17 @@ bool AITASK_IsMarineCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* // Always obey commander orders even if there's a bunch of other marines already there if (!Task->bIssuedByCommander) { - int NumMarinesNearby = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), Task->TaskLocation, UTIL_MetresToGoldSrcUnits(4.0f), false, pBot->Edict, AVH_USER3_NONE); + int DesiredNumCappers = (ResNodeIndex->OwningTeam == AIMGR_GetEnemyTeam(pBot->Player->GetTeam())) ? 2 : 1; + int NumMarinesNearby = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), Task->TaskLocation, UTIL_MetresToGoldSrcUnits(4.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); - if (NumMarinesNearby >= 2 && vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation) > sqrf(UTIL_MetresToGoldSrcUnits(4.0f))) { return false; } + if (NumMarinesNearby >= DesiredNumCappers && vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation) > sqrf(UTIL_MetresToGoldSrcUnits(4.0f))) { return false; } } if (ResNodeIndex->bIsOccupied) { - if (ResNodeIndex->OwningTeam == pBot->Player->GetTeam() && !FNullEnt(ResNodeIndex->ActiveTowerEntity)) + if (ResNodeIndex->OwningTeam == pBot->Player->GetTeam()) { - return !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity); - } - else - { - return true; + return (FNullEnt(ResNodeIndex->ActiveTowerEntity) || !UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity)); } } @@ -876,6 +888,67 @@ bool AITASK_IsReinforceStructureTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTas return false; } +bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) +{ + if (!Task || FNullEnt(Task->TaskTarget) || IsPlayerAlien(pBot->Edict)) { return false; } + + AvHAIHiveDefinition* HiveToSecure = AITAC_GetHiveFromEdict(Task->TaskTarget); + + if (!HiveToSecure || HiveToSecure->Status != HIVE_STATUS_UNBUILT) { return false; } + + // A marine bot will consider their "secure hive" task completed if the following structures have been fully built: + // Phase gate (only if tech available) + // Turret factory (regular or advanced) + // 5 turrets + // Resource node has been capped by the bot's team + + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + + bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(BotTeam); + + bool bHasPhaseGate = false; + bool bHasTurretFactory = false; + bool bTurretFactoryElectrified = false; + int NumTurrets = 0; + + DeployableSearchFilter SearchFilter; + SearchFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); + SearchFilter.DeployableTeam = BotTeam; + SearchFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + SearchFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + SearchFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + vector HiveStructures = AITAC_FindAllDeployables(HiveToSecure->FloorLocation, &SearchFilter); + + for (auto it = HiveStructures.begin(); it != HiveStructures.end(); it++) + { + AvHAIBuildableStructure* Structure = (*it); + + if (Structure->StructureType == STRUCTURE_MARINE_TURRETFACTORY) + { + bHasPhaseGate = true; + } + + if (Structure->StructureType == STRUCTURE_MARINE_TURRETFACTORY || Structure->StructureType == STRUCTURE_MARINE_ADVTURRETFACTORY) + { + bHasTurretFactory = true; + + SearchFilter.DeployableTypes = STRUCTURE_MARINE_TURRET; + SearchFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(8.0f); + + NumTurrets = AITAC_GetNumDeployablesNearLocation(Structure->Location, &SearchFilter); + + } + + } + + const AvHAIResourceNode* ResNode = HiveToSecure->HiveResNodeRef; + + bool bSecuredResNode = (!ResNode || (ResNode->OwningTeam == BotTeam && !FNullEnt(ResNode->ActiveTowerEntity) && UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity))); + + return !((!bPhaseGatesAvailable || bHasPhaseGate) && bHasTurretFactory && NumTurrets >= 5 && bSecuredResNode); +} + bool AITASK_IsEvolveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { if (!Task || Task->Evolution == MESSAGE_NULL || !IsPlayerAlien(pBot->Edict)) { return false; } @@ -1438,6 +1511,88 @@ void BotProgressResupplyTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) } +void AIPlayerBuildStructure(AvHAIPlayer* pBot, edict_t* BuildTarget) +{ + if (!pBot || !IsPlayerActiveInGame(pBot->Edict) || FNullEnt(BuildTarget) || UTIL_StructureIsFullyBuilt(BuildTarget)) { return; } + + if (IsPlayerAlien(pBot->Edict) && !IsPlayerGorge(pBot->Edict)) { return; } + + if (IsPlayerMarine(pBot->Edict)) + { + // If we're not already building + if (pBot->Edict->v.viewmodel != 0) + { + // If someone else is building, then we will guard + edict_t* OtherBuilder = AITAC_GetClosestPlayerOnTeamWithLOS(pBot->Player->GetTeam(), BuildTarget->v.origin, UTIL_MetresToGoldSrcUnits(2.0f), pBot->Edict); + + if (!FNullEnt(OtherBuilder) && OtherBuilder->v.weaponmodel == 0) + { + BotGuardLocation(pBot, BuildTarget->v.origin); + return; + } + } + } + + if (IsPlayerInUseRange(pBot->Edict, BuildTarget)) + { + // If we were ducking before then keep ducking + if (pBot->Edict->v.oldbuttons & IN_DUCK) + { + pBot->Button |= IN_DUCK; + } + + BotUseObject(pBot, BuildTarget, true); + + // Haven't started building, maybe not quite looking at the right angle + if (pBot->Edict->v.weaponmodel != 0) + { + if (vDist2DSq(pBot->Edict->v.origin, BuildTarget->v.origin) > sqrf(60.0f)) + { + MoveDirectlyTo(pBot, BuildTarget->v.origin); + } + else + { + Vector NewViewPoint = UTIL_GetRandomPointInBoundingBox(BuildTarget->v.absmin, BuildTarget->v.absmax); + + BotLookAt(pBot, NewViewPoint); + } + } + + return; + } + else + { + // Might need to duck if it's an infantry portal + if (vDist2DSq(pBot->Edict->v.origin, BuildTarget->v.origin) < sqrf(max_player_use_reach)) + { + if (BuildTarget->v.origin > pBot->Edict->v.origin) + { + BotJump(pBot); + } + else + { + pBot->Button |= IN_DUCK; + } + + } + } + + MoveTo(pBot, BuildTarget->v.origin, MOVESTYLE_NORMAL); + + if (IsPlayerMarine(pBot->Edict)) + { + if (gpGlobals->time - pBot->LastCombatTime > 5.0f) + { + BotReloadWeapons(pBot); + } + } + + if (vDist2DSq(pBot->Edict->v.origin, BuildTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) + { + BotLookAt(pBot, UTIL_GetCentreOfEntity(BuildTarget)); + } +} + void MarineProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { edict_t* pEdict = pBot->Edict; @@ -2314,70 +2469,66 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (!Hive) { return; } - bool bWaitForBuildingPlacement = false; + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); DeployableSearchFilter StructureFilter; - StructureFilter.DeployableTypes = (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); + StructureFilter.DeployableTypes = SEARCH_ALL_MARINE_STRUCTURES; StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); - StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + StructureFilter.DeployableTeam = BotTeam; + StructureFilter.ReachabilityTeam = BotTeam; + StructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_COMPLETED; - AvHAIBuildableStructure* TF = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter); + vector BuildableStructures = AITAC_FindAllDeployables(Hive->FloorLocation, &StructureFilter); - if (!TF || !(TF->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { bWaitForBuildingPlacement = true; } + AvHAIBuildableStructure* StructureToBuild = nullptr; + float MinDist = 0.0f; - bool bPhaseGatesAvailable = AITAC_ResearchIsComplete(pBot->Player->GetTeam(), TECH_PHASE_GATE); - - if (bPhaseGatesAvailable && !bWaitForBuildingPlacement) + for (auto it = BuildableStructures.begin(); it != BuildableStructures.end(); it++) { - StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; - - AvHAIBuildableStructure* PhaseGate = AITAC_FindClosestDeployableToLocation(Hive->FloorLocation, &StructureFilter); - - if (!PhaseGate || !(TF->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { bWaitForBuildingPlacement = true; } - } - - if (!bWaitForBuildingPlacement) - { - StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRET; - - int NumTurrets = AITAC_GetNumDeployablesNearLocation(TF->Location, &StructureFilter); - - if (NumTurrets < 5) { bWaitForBuildingPlacement = true; } - } - - if (bWaitForBuildingPlacement) - { - if (TF) - { - BotGuardLocation(pBot, TF->Location); - } - else - { - BotGuardLocation(pBot, Task->TaskLocation); - } + AvHAIBuildableStructure* ThisStructure = (*it); + if (ThisStructure->StructureType == STRUCTURE_MARINE_PHASEGATE) + { + AIPlayerBuildStructure(pBot, ThisStructure->edict); + return; + } + + float ThisDist = vDist2DSq(pBot->Edict->v.origin, ThisStructure->Location); + + if (!StructureToBuild || ThisDist < MinDist) + { + StructureToBuild = ThisStructure; + MinDist = ThisDist; + } + } + + if (StructureToBuild) + { + AIPlayerBuildStructure(pBot, StructureToBuild->edict); return; } const AvHAIResourceNode* ResNode = Hive->HiveResNodeRef; - if (ResNode && ResNode->OwningTeam != pBot->Player->GetTeam()) + if (ResNode && ResNode->bIsOccupied) { - if (ResNode->bIsOccupied) + if (ResNode->OwningTeam != BotTeam) { BotAttackTarget(pBot, ResNode->ActiveTowerEntity); + return; } else { - BotGuardLocation(pBot, ResNode->Location); + if (!UTIL_StructureIsFullyBuilt(ResNode->ActiveTowerEntity)) + { + AIPlayerBuildStructure(pBot, ResNode->ActiveTowerEntity); + return; + } } - - return; } BotGuardLocation(pBot, Task->TaskLocation); - - } @@ -2413,24 +2564,7 @@ void MarineProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { if (!UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity)) { - // Now we're committed, don't get distracted - Task->bTaskIsUrgent = true; - if (UTIL_PlayerHasLOSToEntity(pBot->Edict, ResNodeIndex->ActiveTowerEntity, max_player_use_reach, true)) - { - BotUseObject(pBot, ResNodeIndex->ActiveTowerEntity, true); - if (vDist2DSq(pBot->Edict->v.origin, ResNodeIndex->ActiveTowerEntity->v.origin) > sqrf(50.0f)) - { - MoveDirectlyTo(pBot, ResNodeIndex->ActiveTowerEntity->v.origin); - } - return; - } - - MoveTo(pBot, ResNodeIndex->ActiveTowerEntity->v.origin, MOVESTYLE_NORMAL); - - if (vDist2DSq(pBot->Edict->v.origin, ResNodeIndex->ActiveTowerEntity->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) - { - BotLookAt(pBot, UTIL_GetCentreOfEntity(ResNodeIndex->ActiveTowerEntity)); - } + AIPlayerBuildStructure(pBot, ResNodeIndex->ActiveTowerEntity); return; } diff --git a/main/source/mod/AIPlayers/AvHAITask.h b/main/source/mod/AIPlayers/AvHAITask.h index 7fca57e4..d7182442 100644 --- a/main/source/mod/AIPlayers/AvHAITask.h +++ b/main/source/mod/AIPlayers/AvHAITask.h @@ -44,6 +44,7 @@ bool AITASK_IsDefendTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); bool AITASK_IsEvolveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); bool AITASK_IsReinforceStructureTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); +bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); bool AITASK_IsAlienGetHealthTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); bool AITASK_IsAlienHealTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); @@ -87,6 +88,8 @@ void MarineProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); void MarineProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); void BotProgressWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); +void AIPlayerBuildStructure(AvHAIPlayer* pBot, edict_t* BuildTarget); + void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task); void AlienProgressGetHealthTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);