diff --git a/main/source/mod/AIPlayers/AvHAICommander.cpp b/main/source/mod/AIPlayers/AvHAICommander.cpp new file mode 100644 index 00000000..8aed44e0 --- /dev/null +++ b/main/source/mod/AIPlayers/AvHAICommander.cpp @@ -0,0 +1,1438 @@ + +#include "AvHAICommander.h" +#include "AvHAITactical.h" +#include "AvHAIMath.h" +#include "AvHAIPlayerUtil.h" +#include "AvHAIWeaponHelper.h" +#include "AvHAINavigation.h" +#include "AvHAITask.h" +#include "AvHAIHelper.h" + +#include "../AvHSharedUtil.h" +#include "../AvHServerUtil.h" + +bool AICOMM_DeployStructure(AvHAIPlayer* pBot, const AvHAIDeployableStructureType StructureToDeploy, const Vector Location) +{ + AvHMessageID StructureID = UTIL_StructureTypeToImpulseCommand(StructureToDeploy); + + Vector BuildLocation = Location; + + if (!AvHSHUGetIsSiteValidForBuild(StructureID, &BuildLocation)) { return false; } + + CBaseEntity* NewBuilding = AvHSUBuildTechForPlayer(StructureID, BuildLocation, pBot->Player); + + pBot->next_commander_action_time = gpGlobals->time + 1.0f; + + return NewBuilding != nullptr; +} + +bool AICOMM_DeployItem(AvHAIPlayer* pBot, const AvHAIDeployableItemType ItemToDeploy, const Vector Location) +{ + AvHMessageID StructureID = UTIL_ItemTypeToImpulseCommand(ItemToDeploy); + + Vector BuildLocation = Location; + + if (!AvHSHUGetIsSiteValidForBuild(StructureID, &BuildLocation)) { return false; } + + CBaseEntity* NewBuilding = AvHSUBuildTechForPlayer(StructureID, BuildLocation, pBot->Player); + + pBot->next_commander_action_time = gpGlobals->time + 0.2f; + + return NewBuilding != nullptr; +} + +bool AICOMM_ResearchTech(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToResearch, AvHMessageID Research) +{ + if (FNullEnt(StructureToResearch->edict)) { return false; } + + if (StructureToResearch->EntityRef->GetIsRecycling() || StructureToResearch->EntityRef->GetIsResearching()) { return false; } + + int StructureIndex = ENTINDEX(StructureToResearch->edict); + + if (StructureIndex < 0) { return false; } + + pBot->Player->SetSelection(StructureIndex, true); + + pBot->Button |= IN_ATTACK2; + pBot->Impulse = Research; + + pBot->next_commander_action_time = gpGlobals->time + 1.0f; + + return true; +} + +bool AICOMM_UpgradeStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToUpgrade) +{ + AvHMessageID UpgradeImpulse = MESSAGE_NULL; + + switch (StructureToUpgrade->StructureType) + { + case STRUCTURE_MARINE_ARMOURY: + UpgradeImpulse = ARMORY_UPGRADE; + break; + case STRUCTURE_MARINE_TURRETFACTORY: + UpgradeImpulse = TURRET_FACTORY_UPGRADE; + default: + return false; + } + + return AICOMM_ResearchTech(pBot, StructureToUpgrade, UpgradeImpulse); +} + +bool AICOMM_RecycleStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToRecycle) +{ + return AICOMM_ResearchTech(pBot, StructureToRecycle, BUILD_RECYCLE); +} + +bool AICOMM_IssueMovementOrder(AvHAIPlayer* pBot, edict_t* Recipient, const Vector MoveLocation) +{ + if (FNullEnt(Recipient) || !IsPlayerActiveInGame(Recipient) || vIsZero(MoveLocation)) { return false; } + + int ReceiverIndex = ENTINDEX(Recipient); + + if (ReceiverIndex <= 0) { return false; } + + AvHOrder NewOrder; + + NewOrder.SetOrderType(ORDERTYPEL_MOVE); + NewOrder.SetReceiver(ReceiverIndex); + NewOrder.SetLocation(MoveLocation); + NewOrder.SetOrderID(); + + pBot->Player->SetSelection(ReceiverIndex, true); + + pBot->Player->GiveOrderToSelection(NewOrder); + + return true; +} + +bool AICOMM_IssueBuildOrder(AvHAIPlayer* pBot, edict_t* Recipient, edict_t* TargetStructure) +{ + if (FNullEnt(Recipient) || !IsPlayerActiveInGame(Recipient) || FNullEnt(TargetStructure) || UTIL_StructureIsFullyBuilt(TargetStructure)) { return false; } + + int ReceiverIndex = ENTINDEX(Recipient); + int TargetIndex = ENTINDEX(TargetStructure); + + if (ReceiverIndex <= 0 || TargetIndex <= 0) { return false; } + + AvHBaseBuildable* BuildingRef = dynamic_cast(CBaseEntity::Instance(TargetStructure)); + + if (!BuildingRef) { return false; } + + AvHOrder NewOrder; + + NewOrder.SetOrderType(ORDERTYPET_BUILD); + NewOrder.SetReceiver(ReceiverIndex); + NewOrder.SetTargetIndex(TargetIndex); + NewOrder.SetUser3TargetType((AvHUser3)TargetStructure->v.iuser3); + NewOrder.SetOrderTargetType(ORDERTARGETTYPE_LOCATION); + NewOrder.SetLocation(TargetStructure->v.origin); + NewOrder.SetOrderID(); + + pBot->Player->SetSelection(ReceiverIndex, true); + + pBot->Player->GiveOrderToSelection(NewOrder); + + return true; +} + +bool AICOMM_IssueSecureHiveOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIHiveDefinition* HiveToSecure) +{ + if (!HiveToSecure || FNullEnt(Recipient) || !IsPlayerActiveInGame(Recipient)) { return false; } + + int ReceiverIndex = ENTINDEX(Recipient); + + if (ReceiverIndex <= 0) { return false; } + + AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); + + bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(CommanderTeam); + + if (bPhaseGatesAvailable) + { + DeployableSearchFilter PGFilter; + PGFilter.DeployableTeam = CommanderTeam; + PGFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; + PGFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + PGFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + PGFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + bool bPhaseExists = AITAC_DeployableExistsAtLocation(HiveToSecure->FloorLocation, &PGFilter); + + if (!bPhaseExists) + { + Vector OrderLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), HiveToSecure->FloorLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + + return AICOMM_IssueMovementOrder(pBot, Recipient, OrderLocation); + } + } + + DeployableSearchFilter TFFilter; + TFFilter.DeployableTeam = CommanderTeam; + TFFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY; + TFFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + TFFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + TFFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + AvHAIBuildableStructure* TF = AITAC_FindClosestDeployableToLocation(HiveToSecure->FloorLocation, &TFFilter); + + if (!TF) + { + Vector OrderLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), HiveToSecure->FloorLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + + return AICOMM_IssueMovementOrder(pBot, Recipient, OrderLocation); + } + + DeployableSearchFilter TurretFilter; + TurretFilter.DeployableTeam = CommanderTeam; + TurretFilter.DeployableTypes = STRUCTURE_MARINE_TURRET; + TurretFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + TurretFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + TurretFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + int NumTurrets = AITAC_GetNumDeployablesNearLocation(TF->Location, &TurretFilter); + + if (NumTurrets < 5) + { + Vector OrderLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), HiveToSecure->FloorLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + + return AICOMM_IssueMovementOrder(pBot, Recipient, OrderLocation); + } + + AvHAIResourceNode* HiveNode = HiveToSecure->HiveResNodeRef; + + if (HiveNode && (FNullEnt(HiveNode->ActiveTowerEntity) || HiveNode->ActiveTowerEntity->v.team != CommanderTeam || !UTIL_StructureIsFullyBuilt(HiveNode->ActiveTowerEntity))) + { + Vector OrderLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), HiveNode->Location, UTIL_MetresToGoldSrcUnits(2.0f)); + + return AICOMM_IssueMovementOrder(pBot, Recipient, OrderLocation); + } + + Vector OrderLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), HiveToSecure->FloorLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + + return AICOMM_IssueMovementOrder(pBot, Recipient, OrderLocation); + +} + +bool AICOMM_IssueSiegeHiveOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIHiveDefinition* HiveToSiege, const Vector SiegePosition) +{ + if (!HiveToSiege || FNullEnt(Recipient) || !IsPlayerActiveInGame(Recipient)) { return false; } + + return AICOMM_IssueMovementOrder(pBot, Recipient, SiegePosition); +} + +bool AICOMM_IssueSecureResNodeOrder(AvHAIPlayer* pBot, edict_t* Recipient, const AvHAIResourceNode* ResNode) +{ + if (!ResNode || FNullEnt(Recipient) || !IsPlayerActiveInGame(Recipient)) { return false; } + + Vector MoveLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), ResNode->Location, UTIL_MetresToGoldSrcUnits(3.0f)); + + if (MoveLocation == ZERO_VECTOR) + { + MoveLocation = ResNode->Location; + } + + return AICOMM_IssueMovementOrder(pBot, Recipient, MoveLocation); +} + +ai_commander_request* AICOMM_GetExistingRequestForPlayer(AvHAIPlayer* pBot, edict_t* Requestor) +{ + for (auto it = pBot->ActiveRequests.begin(); it != pBot->ActiveRequests.end(); it++) + { + if (it->Requestor == Requestor) + { + return &(*it); + } + } + + return nullptr; +} + +void AICOMM_CheckNewRequests(AvHAIPlayer* pBot) +{ + // Clear all expired requests + for (auto it = pBot->ActiveRequests.begin(); it != pBot->ActiveRequests.end();) + { + if (gpGlobals->time - it->RequestTime > BALANCE_VAR(kAlertExpireTime)) + { + it = pBot->ActiveRequests.erase(it); + } + else + { + it++; + } + } + + AvHTeam* TeamRef = pBot->Player->GetTeamPointer(); + + AlertListType HealthRequests = TeamRef->GetAlerts(COMMANDER_NEXTHEALTH); + AlertListType AmmoRequests = TeamRef->GetAlerts(COMMANDER_NEXTAMMO); + AlertListType OrderRequests = TeamRef->GetAlerts(COMMANDER_NEXTIDLE); + + // Cycle through all active health requests and see if any are new or overriding existing ones + for (auto it = HealthRequests.begin(); it != HealthRequests.end(); it++) + { + edict_t* Requestor = INDEXENT(it->GetEntityIndex()); + + ai_commander_request* ExistingRequest = AICOMM_GetExistingRequestForPlayer(pBot, Requestor); + + // This is a new request from the player, update our list accordingly + if (!ExistingRequest || ExistingRequest->RequestTime < it->GetTime()) + { + ai_commander_request NewRequest; + ai_commander_request* RequestRef = (ExistingRequest) ? ExistingRequest : &NewRequest; + + RequestRef->bNewRequest = true; + RequestRef->bResponded = false; + RequestRef->RequestTime = it->GetTime(); + RequestRef->RequestType = COMMANDER_NEXTHEALTH; + RequestRef->Requestor = Requestor; + + if (!ExistingRequest) + { + pBot->ActiveRequests.push_back(NewRequest); + } + } + } + + // Do same for ammo requests + for (auto it = AmmoRequests.begin(); it != AmmoRequests.end(); it++) + { + edict_t* Requestor = INDEXENT(it->GetEntityIndex()); + + ai_commander_request* ExistingRequest = AICOMM_GetExistingRequestForPlayer(pBot, Requestor); + + if (!ExistingRequest || ExistingRequest->RequestTime < it->GetTime()) + { + ai_commander_request NewRequest; + ai_commander_request* RequestRef = (ExistingRequest) ? ExistingRequest : &NewRequest; + + RequestRef->bNewRequest = true; + RequestRef->bResponded = false; + RequestRef->RequestTime = it->GetTime(); + RequestRef->RequestType = COMMANDER_NEXTAMMO; + RequestRef->Requestor = Requestor; + + if (!ExistingRequest) + { + pBot->ActiveRequests.push_back(NewRequest); + } + } + } + + // And for order requests + for (auto it = OrderRequests.begin(); it != OrderRequests.end(); it++) + { + edict_t* Requestor = INDEXENT(it->GetEntityIndex()); + + ai_commander_request* ExistingRequest = AICOMM_GetExistingRequestForPlayer(pBot, Requestor); + + if (!ExistingRequest || ExistingRequest->RequestTime < it->GetTime()) + { + ai_commander_request NewRequest; + ai_commander_request* RequestRef = (ExistingRequest) ? ExistingRequest : &NewRequest; + + RequestRef->bNewRequest = true; + RequestRef->bResponded = false; + RequestRef->RequestTime = it->GetTime(); + RequestRef->RequestType = COMMANDER_NEXTIDLE; + RequestRef->Requestor = Requestor; + + if (!ExistingRequest) + { + pBot->ActiveRequests.push_back(NewRequest); + } + } + } +} + +bool AICOMM_IsRequestValid(ai_commander_request* Request) +{ + if (Request->bResponded) { return false; } + + switch (Request->RequestType) + { + case COMMANDER_NEXTHEALTH: + return Request->Requestor->v.health < Request->Requestor->v.max_health; + default: + return true; + } + + return true; +} + +bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action) +{ + + AvHTeamNumber TeamNumber = pBot->Player->GetTeam(); + + edict_t* CommChair = AITAC_GetCommChair(TeamNumber); + + if (FNullEnt(CommChair)) { return false; } + + DeployableSearchFilter StructureFilter; + StructureFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + StructureFilter.DeployableTeam = TeamNumber; + StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + StructureFilter.ReachabilityTeam = TeamNumber; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + int NumInfantryPortals = AITAC_GetNumDeployablesNearLocation(CommChair->v.origin, &StructureFilter); + + if (NumInfantryPortals < 2) + { + if (AICOMM_BuildInfantryPortal(pBot, CommChair, Action)) + { + return true; + } + } + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); + + AvHAIBuildableStructure* BaseArmoury = AITAC_FindClosestDeployableToLocation(CommChair->v.origin, &StructureFilter); + + if (!BaseArmoury) + { + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + AvHAIBuildableStructure* NearestInfantryPortal = AITAC_FindClosestDeployableToLocation(CommChair->v.origin, &StructureFilter); + + Vector BuildLocation = ZERO_VECTOR; + + if (NearestInfantryPortal) + { + BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestInfantryPortal->Location, UTIL_MetresToGoldSrcUnits(5.0f)); + } + + if (vIsZero(BuildLocation)) + { + BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), CommChair->v.origin, UTIL_MetresToGoldSrcUnits(10.0f)); + } + + if (!vIsZero(BuildLocation)) + { + if (AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_ARMOURY, BuildLocation)) + { + return true; + } + } + } + + const AvHAIHiveDefinition* HiveUnderSiege = AITAC_GetNearestHiveUnderActiveSiege(pBot->Player->GetTeam(), AITAC_GetCommChairLocation(pBot->Player->GetTeam())); + + if (HiveUnderSiege) + { + bool bAlreadyScanning = AITAC_ItemExistsInLocation(HiveUnderSiege->Location, DEPLOYABLE_ITEM_SCAN, pBot->Player->GetTeam(), AI_REACHABILITY_NONE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); + + if (!bAlreadyScanning) + { + Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ALL_NAV_PROFILE), HiveUnderSiege->FloorLocation, UTIL_MetresToGoldSrcUnits(3.0f)); + + if (AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_SCAN, BuildLocation)) + { + return true; + } + } + } + + if (AITAC_PhaseGatesAvailable(TeamNumber)) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + bool bPhaseNearBase = AITAC_DeployableExistsAtLocation(CommChair->v.origin, &StructureFilter); + + if (!bPhaseNearBase) + { + Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), CommChair->v.origin, UTIL_MetresToGoldSrcUnits(10.0f)); + + if (AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation)) + { + return true; + } + } + } + + const AvHAIResourceNode* CappableNode = AICOMM_GetNearestResourceNodeCapOpportunity(TeamNumber, CommChair->v.origin); + + if (CappableNode) + { + if (AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_RESTOWER, CappableNode->Location)) + { + return true; + } + } + + const AvHAIHiveDefinition* HiveToSecure = AICOMM_GetEmptyHiveOpportunityNearestLocation(pBot, AITAC_GetCommChairLocation(TeamNumber)); + + if (HiveToSecure) + { + if (AICOMM_PerformNextSecureHiveAction(pBot, HiveToSecure, Action)) + { + return true; + } + } + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB; + StructureFilter.MaxSearchRadius = 0.0f; + + bool bHasArmsLab = AITAC_DeployableExistsAtLocation(CommChair->v.origin, &StructureFilter); + + if (!bHasArmsLab) + { + Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), CommChair->v.origin, UTIL_MetresToGoldSrcUnits(10.0f)); + + if (!vIsZero(BuildLocation)) + { + if (AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_ARMSLAB, BuildLocation)) + { + return true; + } + } + } + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_OBSERVATORY; + + bool bHasObservatory = AITAC_DeployableExistsAtLocation(CommChair->v.origin, &StructureFilter); + + if (!bHasObservatory) + { + Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), CommChair->v.origin, UTIL_MetresToGoldSrcUnits(10.0f)); + + if (!vIsZero(BuildLocation)) + { + if (AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_OBSERVATORY, BuildLocation)) + { + return true; + } + } + } + + if (!AITAC_ResearchIsComplete(TeamNumber, TECH_RESEARCH_PHASETECH)) + { + return false; + } + + const AvHAIHiveDefinition* HiveToSiege = AICOMM_GetHiveSiegeOpportunityNearestLocation(pBot, AITAC_GetCommChairLocation(TeamNumber)); + + if (HiveToSiege) + { + if (AICOMM_PerformNextSiegeHiveAction(pBot, HiveToSiege, Action)) + { + return true; + } + } + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ADVARMOURY; + + bool bHasAdvArmoury = AITAC_DeployableExistsAtLocation(CommChair->v.origin, &StructureFilter); + bool bIsResearchingArmoury = false; + + if (!bHasAdvArmoury) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMOURY; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + StructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* NearestArmoury = AITAC_FindClosestDeployableToLocation(CommChair->v.origin, &StructureFilter); + + if (NearestArmoury) + { + bIsResearchingArmoury = UTIL_StructureIsUpgrading(NearestArmoury->edict); + + if (!bIsResearchingArmoury) + { + if (AICOMM_UpgradeStructure(pBot, NearestArmoury)) + { + return true; + } + } + } + } + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_PROTOTYPELAB; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + StructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_NONE; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + bool bHasPrototypeLab = AITAC_DeployableExistsAtLocation(CommChair->v.origin, &StructureFilter); + + if (!bHasPrototypeLab && bHasAdvArmoury) + { + Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInDonut(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), BaseArmoury->Location, UTIL_MetresToGoldSrcUnits(3.0f), UTIL_MetresToGoldSrcUnits(5.0f)); + + if (!vIsZero(BuildLocation)) + { + if (AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PROTOTYPELAB, BuildLocation)) + { + return true; + } + } + } + + int Resources = pBot->Player->GetResources(); + + if (Resources > 100) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_RESTOWER; + StructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING | STRUCTURE_STATUS_ELECTRIFIED; + StructureFilter.MaxSearchRadius = 0.0f; + + AvHAIBuildableStructure* ResTower = AITAC_FindFurthestDeployableFromLocation(CommChair->v.origin, &StructureFilter); + + if (ResTower && AITAC_ElectricalResearchIsAvailable(ResTower->edict)) + { + if (AICOMM_ResearchTech(pBot, ResTower, RESEARCH_ELECTRICAL)) + { + return true; + } + } + } + + return false; +} + +bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot, commander_action* Action) +{ + AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); + + DeployableSearchFilter StructureFilter; + StructureFilter.DeployableTeam = CommanderTeam; + StructureFilter.ReachabilityTeam = CommanderTeam; + StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_GRENADES)) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY; + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* Armoury = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (Armoury) + { + return AICOMM_ResearchTech(pBot, Armoury, RESEARCH_GRENADES); + } + } + + if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_ARMOR_ONE)) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB; + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* ArmsLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (ArmsLab) + { + return AICOMM_ResearchTech(pBot, ArmsLab, RESEARCH_ARMOR_ONE); + } + } + + if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_WEAPONS_ONE)) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB; + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* ArmsLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (ArmsLab) + { + return AICOMM_ResearchTech(pBot, ArmsLab, RESEARCH_WEAPONS_ONE); + } + } + + if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_PHASETECH)) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_OBSERVATORY; + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* Observatory = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (Observatory) + { + return AICOMM_ResearchTech(pBot, Observatory, RESEARCH_PHASETECH); + } + } + + if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_MOTIONTRACK)) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_OBSERVATORY; + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* Observatory = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (Observatory) + { + return AICOMM_ResearchTech(pBot, Observatory, RESEARCH_MOTIONTRACK); + } + } + + if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_ARMOR_TWO)) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB; + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* ArmsLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (ArmsLab) + { + return AICOMM_ResearchTech(pBot, ArmsLab, RESEARCH_ARMOR_TWO); + } + } + + if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_WEAPONS_TWO)) + { + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB; + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* ArmsLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (ArmsLab) + { + return AICOMM_ResearchTech(pBot, ArmsLab, RESEARCH_WEAPONS_TWO); + } + } + + if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_CATALYSTS)) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB; + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* ArmsLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (ArmsLab) + { + return AICOMM_ResearchTech(pBot, ArmsLab, RESEARCH_CATALYSTS); + } + } + + if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_HEAVYARMOR)) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_PROTOTYPELAB; + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* ProtoLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (ProtoLab) + { + return AICOMM_ResearchTech(pBot, ProtoLab, RESEARCH_HEAVYARMOR); + } + } + + if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_JETPACKS)) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_PROTOTYPELAB; + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* ProtoLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (ProtoLab) + { + return AICOMM_ResearchTech(pBot, ProtoLab, RESEARCH_JETPACKS); + } + } + + if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_ARMOR_THREE)) + { + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB; + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* ArmsLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (ArmsLab) + { + return AICOMM_ResearchTech(pBot, ArmsLab, RESEARCH_ARMOR_THREE); + } + } + + if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_WEAPONS_THREE)) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB; + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + + AvHAIBuildableStructure* ArmsLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + + if (ArmsLab) + { + return AICOMM_ResearchTech(pBot, ArmsLab, RESEARCH_WEAPONS_THREE); + } + } + + return false; +} + +const AvHAIHiveDefinition* AICOMM_GetHiveSiegeOpportunityNearestLocation(AvHAIPlayer* CommanderBot, const Vector SearchLocation) +{ + AvHTeamNumber CommanderTeam = CommanderBot->Player->GetTeam(); + + bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(CommanderTeam); + + if (!bPhaseGatesAvailable) { return nullptr; } + + const AvHAIHiveDefinition* Result = nullptr; + float MinDist = 0.0f; + + const vector Hives = AITAC_GetAllHives(); + + for (auto it = Hives.begin(); it != Hives.end(); it++) + { + const AvHAIHiveDefinition* Hive = &(*it); + + if (it->Status == HIVE_STATUS_UNBUILT) { continue; } + + if (AITAC_GetMarineEligibleToBuildSiege(CommanderTeam, Hive) == nullptr) { continue; } + + DeployableSearchFilter StructureFilter; + StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + AvHAIBuildableStructure* BuiltPhaseGate = AITAC_FindClosestDeployableToLocation(Hive->Location, &StructureFilter); + + if (BuiltPhaseGate) + { + int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(CommanderTeam, BuiltPhaseGate->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, CommanderBot->Edict, AVH_USER3_COMMANDER_PLAYER); + + if (NumBuilders == 0) { continue; } + } + + float ThisDist = vDist2DSq(Hive->FloorLocation, SearchLocation); + + if (!Result || ThisDist < MinDist) + { + Result = Hive; + MinDist = ThisDist; + } + + } + + return Result; +} + +const AvHAIResourceNode* AICOMM_GetNearestResourceNodeCapOpportunity(const AvHTeamNumber Team, const Vector SearchLocation) +{ + vector AllNodes = AITAC_GetAllResourceNodes(); + + AvHAIResourceNode* Result = nullptr; + float MinDist = 0.0f; + + for (auto it = AllNodes.begin(); it != AllNodes.end(); it++) + { + if (it->bIsOccupied) { continue; } + + if (!AITAC_AnyPlayerOnTeamWithLOS(Team, (it->Location + Vector(0.0f, 0.0f, 32.0f)), UTIL_MetresToGoldSrcUnits(5.0f))) { continue; } + + float ThisDist = vDist2DSq(it->Location, SearchLocation); + + if (!Result || ThisDist < MinDist) + { + Result = &(*it); + MinDist = ThisDist; + } + } + + return Result; +} + +bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege, commander_action* Action) +{ + AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); + + DeployableSearchFilter StructureFilter; + StructureFilter.DeployableTeam = CommanderTeam; + StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + StructureFilter.ReachabilityTeam = CommanderTeam; + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); + + Vector SiegeLocation; + AvHAIBuildableStructure* ExistingPG = nullptr; + + if (AITAC_PhaseGatesAvailable(CommanderTeam)) + { + StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; + + ExistingPG = AITAC_FindClosestDeployableToLocation(HiveToSiege->Location, &StructureFilter); + + if (ExistingPG && (ExistingPG->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) + { + SiegeLocation = ExistingPG->Location; + } + + } + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY; + + AvHAIBuildableStructure* ExistingTF = AITAC_FindClosestDeployableToLocation(HiveToSiege->Location, &StructureFilter); + + if (vIsZero(SiegeLocation)) + { + if (ExistingTF) + { + SiegeLocation = ExistingTF->Location; + } + else + { + edict_t* NearestBuilder = AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, HiveToSiege->Location, UTIL_MetresToGoldSrcUnits(20.0f)); + + if (FNullEnt(NearestBuilder)) { return false; } + + SiegeLocation = NearestBuilder->v.origin; + } + } + + edict_t* NearestBuilder = AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, SiegeLocation, UTIL_MetresToGoldSrcUnits(20.0f)); + + if (FNullEnt(NearestBuilder)) { return false; } + + Vector NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(BUILDING_NAV_MESH), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f)); + + if (!ExistingPG) + { + return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, NextBuildPosition); + } + + if (!ExistingTF) + { + return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, NextBuildPosition); + } + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); + + AvHAIBuildableStructure* ExistingArmoury = AITAC_FindClosestDeployableToLocation(SiegeLocation, &StructureFilter); + + if (!ExistingArmoury) + { + return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_ARMOURY, NextBuildPosition); + } + + if (ExistingTF->StructureType != STRUCTURE_MARINE_ADVARMOURY) + { + return AICOMM_UpgradeStructure(pBot, ExistingTF); + } + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_SIEGETURRET; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + int NumSiegeTurrets = AITAC_GetNumDeployablesNearLocation(ExistingTF->Location, &StructureFilter); + + if (NumSiegeTurrets == 0 || (NumSiegeTurrets < 5 && UTIL_IsStructureElectrified(ExistingTF->edict))) + { + SiegeLocation = ExistingTF->Location; + + NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(BUILDING_NAV_MESH), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f)); + + return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_SIEGETURRET, NextBuildPosition); + } + + if (!UTIL_IsStructureElectrified(ExistingTF->edict)) + { + return AICOMM_ResearchTech(pBot, ExistingTF, RESEARCH_ELECTRICAL); + } + + return false; + +} + +bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege, commander_action* Action) +{ + return false; +} + +bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair, commander_action* Action) +{ + if (FNullEnt(CommChair) || !UTIL_StructureIsFullyBuilt(CommChair)) { return false; } + + Vector BuildLocation = ZERO_VECTOR; + + DeployableSearchFilter ExistingPortalFilter; + ExistingPortalFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; + ExistingPortalFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + ExistingPortalFilter.DeployableTeam = pBot->Player->GetTeam(); + ExistingPortalFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + ExistingPortalFilter.ReachabilityTeam = pBot->Player->GetTeam(); + + AvHAIBuildableStructure* ExistingInfantryPortal = AITAC_FindClosestDeployableToLocation(CommChair->v.origin, &ExistingPortalFilter); + + // First see if we can place the next infantry portal next to the first one + if (ExistingInfantryPortal) + { + BuildLocation = UTIL_GetRandomPointOnNavmeshInDonut(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), ExistingInfantryPortal->edict->v.origin, UTIL_MetresToGoldSrcUnits(2.0f), UTIL_MetresToGoldSrcUnits(3.0f)); + } + + if (vIsZero(BuildLocation)) + { + Vector SearchPoint = ZERO_VECTOR; + + DeployableSearchFilter ResNodeFilter; + ResNodeFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + ResNodeFilter.ReachabilityTeam = pBot->Player->GetTeam(); + + const AvHAIResourceNode* ResNode = AITAC_FindNearestResourceNodeToLocation(CommChair->v.origin, &ResNodeFilter); + + if (ResNode) + { + SearchPoint = ResNode->Location; + } + else + { + return false; + } + + Vector NearestPointToChair = FindClosestNavigablePointToDestination(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), SearchPoint, CommChair->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + + if (NearestPointToChair != ZERO_VECTOR) + { + float Distance = vDist2D(NearestPointToChair, CommChair->v.origin); + float RandomDist = UTIL_MetresToGoldSrcUnits(5.0f) - Distance; + + BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestPointToChair, RandomDist); + + } + else + { + BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), CommChair->v.origin, UTIL_MetresToGoldSrcUnits(5.0f)); + } + } + + if (vIsZero(BuildLocation)) { return false; } + + return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_INFANTRYPORTAL, BuildLocation); +} + +bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot) +{ + AICOMM_CheckNewRequests(pBot); + + ai_commander_request* NextRequest = nullptr; + + float OldestTime = 0.0f; + + // Find the oldest request we haven't fulfilled yet + for (auto it = pBot->ActiveRequests.begin(); it != pBot->ActiveRequests.end(); it++) + { + // Ignore if we've already responded, or the request is too new (leave a nice 1 second response time to requests) + if (it->bResponded || (gpGlobals->time - it->RequestTime) < 1.0f) { continue; } + + // Ignore the request if it's not valid (e.g. request for health while not injured) + if (!AICOMM_IsRequestValid(&(*it))) + { + it->bResponded = true; + continue; + } + + float ThisTime = gpGlobals->time - it->RequestTime; + + if (ThisTime > OldestTime) + { + NextRequest = &(*it); + OldestTime = ThisTime; + } + + } + + // We didn't find any requests outstanding + if (!NextRequest) { return false; } + + edict_t* Requestor = NextRequest->Requestor; + + if (FNullEnt(Requestor) || !IsEdictPlayer(Requestor) || !IsPlayerActiveInGame(Requestor)) + { + NextRequest->bResponded = true; + return false; + } + + int NumDesiredHealthPacks = 0; + int NumDesiredAmmoPacks = 0; + + float RequestorHealthDeficit = Requestor->v.max_health - Requestor->v.health; + + float HealthPerPack = BALANCE_VAR(kPointsPerHealth); + + if (RequestorHealthDeficit > 10.0f) + { + NumDesiredHealthPacks = (int)ceilf(RequestorHealthDeficit / HealthPerPack); + + int NumHealthPacksPresent = AITAC_GetNumItemsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_HEALTHPACK, (AvHTeamNumber)Requestor->v.team, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); + NumDesiredHealthPacks -= NumHealthPacksPresent; + } + + if (NumDesiredHealthPacks > 0) + { + Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(2.0f)); + bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_HEALTHPACK, DeployLocation); + + if (bSuccess) + { + if (NextRequest->RequestType == COMMANDER_NEXTHEALTH && NumDesiredHealthPacks <= 1) + { + NextRequest->bResponded = true; + } + } + + return true; + } + else + { + if (NextRequest->RequestType == COMMANDER_NEXTHEALTH) + { + NextRequest->bResponded = true; + return false; + } + } + + if (NextRequest->RequestType == COMMANDER_NEXTAMMO) + { + AvHPlayer* thePlayer = dynamic_cast(CBaseEntity::Instance(Requestor)); + + bool bFillPrimaryWeapon = true; + + AvHAIWeapon WeaponType = UTIL_GetPlayerPrimaryWeapon(thePlayer); + + if (WeaponType == WEAPON_INVALID) + { + bFillPrimaryWeapon = false; + WeaponType = UTIL_GetPlayerSecondaryWeapon(thePlayer); + } + + if (WeaponType == WEAPON_INVALID) + { + NextRequest->bResponded = true; + return false; + } + + int AmmoDeficit = (bFillPrimaryWeapon) ? (UTIL_GetPlayerPrimaryMaxAmmoReserve(thePlayer) - UTIL_GetPlayerPrimaryAmmoReserve(thePlayer)) : (UTIL_GetPlayerSecondaryMaxAmmoReserve(thePlayer) - UTIL_GetPlayerSecondaryAmmoReserve(thePlayer)); + int WeaponClipSize = (bFillPrimaryWeapon) ? UTIL_GetPlayerPrimaryWeaponMaxClipSize(thePlayer) : UTIL_GetPlayerSecondaryWeaponMaxClipSize(thePlayer); + + if (AmmoDeficit == 0) + { + NextRequest->bResponded = true; + return false; + } + + int DesiredNumAmmoPacks = (int)(ceilf((float)AmmoDeficit / (float)WeaponClipSize)); + DesiredNumAmmoPacks = clampi(DesiredNumAmmoPacks, 0, 5); + + int NumAmmoPacksPresent = AITAC_GetNumItemsInLocation(Requestor->v.origin, DEPLOYABLE_ITEM_AMMO, (AvHTeamNumber)Requestor->v.team, AI_REACHABILITY_MARINE, 0.0f, UTIL_MetresToGoldSrcUnits(5.0f), false); + DesiredNumAmmoPacks -= NumAmmoPacksPresent; + + if (DesiredNumAmmoPacks > 0) + { + Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), Requestor->v.origin, UTIL_MetresToGoldSrcUnits(2.0f)); + bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_AMMO, DeployLocation); + + if (bSuccess) + { + if (DesiredNumAmmoPacks <= 1) + { + NextRequest->bResponded = true; + } + } + + return true; + } + else + { + NextRequest->bResponded = true; + } + + return false; + } + + if (NextRequest->RequestType == COMMANDER_NEXTIDLE) + { + NextRequest->bResponded = true; + } + + + return false; +} + +void AICOMM_SetDropHealthAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient) +{ + AICOMM_ClearAction(Action); + + Action->ActionType = ACTION_DEPLOY; + Action->ActionTarget = Recipient; + Action->ItemToPlace = DEPLOYABLE_ITEM_HEALTHPACK; + Action->NumDesiredInstances = (Recipient->v.health < 50.0f) ? 2 : 1; +} + +void AICOMM_SetDropAmmoAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient) +{ + AICOMM_ClearAction(Action); + + Action->ActionType = ACTION_DEPLOY; + Action->ActionTarget = Recipient; + Action->ItemToPlace = DEPLOYABLE_ITEM_AMMO; + Action->NumDesiredInstances = 4; +} + +void AICOMM_SetDeployStructureAction(AvHAIPlayer* pBot, commander_action* Action, AvHAIDeployableStructureType StructureToBuild, const Vector Location, bool bIsUrgent) +{ + AICOMM_ClearAction(Action); + + Action->ActionType = ACTION_DEPLOY; + Action->StructureToBuild = StructureToBuild; + Action->BuildLocation = Location; + Action->NumDesiredInstances = 1; + Action->bIsActionUrgent = bIsUrgent; +} + +void AICOMM_SetDeployItemAction(AvHAIPlayer* pBot, commander_action* Action, AvHAIDeployableItemType ItemToBuild, const Vector Location, bool bIsUrgent) +{ + AICOMM_ClearAction(Action); + + Action->ActionType = ACTION_DEPLOY; + Action->ItemToPlace = ItemToBuild; + Action->BuildLocation = Location; + Action->NumDesiredInstances = 1; + Action->bIsActionUrgent = bIsUrgent; +} + +void AICOMM_ClearAction(commander_action* Action) +{ + memset(Action, 0, sizeof(commander_action)); +} + +void AICOMM_CommanderThink(AvHAIPlayer* pBot) +{ + // Thanks to EterniumDev (Alien) for the suggestion to have the commander jump out and build if nobody is around to help + if (AICOMM_ShouldCommanderLeaveChair(pBot)) + { + if (IsPlayerCommander(pBot->Edict)) + { + BotStopCommanderMode(pBot); + return; + } + + DeployableSearchFilter StructureFilter; + StructureFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); + StructureFilter.DeployableTeam = pBot->Player->GetTeam(); + StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + StructureFilter.ReachabilityTeam = pBot->Player->GetTeam(); + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_COMPLETED | STRUCTURE_STATUS_RECYCLING; + + AvHAIBuildableStructure* NearestUnbuiltStructure = AITAC_FindClosestDeployableToLocation(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), &StructureFilter); + + if (NearestUnbuiltStructure) + { + AITASK_SetBuildTask(pBot, &pBot->PrimaryBotTask, NearestUnbuiltStructure->edict, false); + } + + BotProgressTask(pBot, &pBot->PrimaryBotTask); + + return; + } + + if (!IsPlayerCommander(pBot->Edict)) + { + BotProgressTakeCommandTask(pBot); + return; + } + + if (gpGlobals->time < pBot->next_commander_action_time) { return; } + + if (AICOMM_CheckForNextSupportAction(pBot)) { return; } + if (AICOMM_CheckForNextBuildAction(pBot, &pBot->BuildAction)) { return; } + if (AICOMM_CheckForNextResearchAction(pBot, &pBot->ResearchAction)) { return; } +} + +bool AICOMM_IsCommanderActionValid(AvHAIPlayer* pBot, commander_action* Action) +{ + if (Action->NumActionAttempts > 5) { return false; } + + if (Action->bIsAwaitingBuildLink) { return true; } + + switch (Action->ActionType) + { + case ACTION_RECYCLE: + return !FNullEnt(Action->ActionTarget) && AvHSHUGetIsMarineStructure((AvHUser3)Action->ActionTarget->v.iuser3) && !UTIL_StructureIsRecycling(Action->ActionTarget); + case ACTION_UPGRADE: + return !FNullEnt(Action->ActionTarget) && AITAC_StructureCanBeUpgraded(Action->ActionTarget); + case ACTION_DEPLOY: + return (Action->NumInstances < Action->NumDesiredInstances); + case ACTION_RESEARCH: + { + if (Action->ResearchId == RESEARCH_ELECTRICAL) + { + return AITAC_ElectricalResearchIsAvailable(Action->ActionTarget); + } + return (AITAC_MarineResearchIsAvailable(pBot->Player->GetTeam(), Action->ResearchId) && !FNullEnt(Action->ActionTarget)); + } + case ACTION_GIVEORDER: + return Action->AssignedPlayer > -1; + default: + return false; + } + + return false; +} + +bool AICOMM_ShouldCommanderLeaveChair(AvHAIPlayer* pBot) +{ + 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; } + + DeployableSearchFilter StructureFilter; + StructureFilter.DeployableTypes = SEARCH_ALL_STRUCTURES; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); + StructureFilter.DeployableTeam = pBot->Player->GetTeam(); + StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + StructureFilter.ReachabilityTeam = pBot->Player->GetTeam(); + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_COMPLETED | STRUCTURE_STATUS_RECYCLING; + + int NumUnbuiltStructuresInBase = AITAC_GetNumDeployablesNearLocation(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), &StructureFilter); + + if (NumUnbuiltStructuresInBase == 0) { return false; } + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_INFANTRYPORTAL; + StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + StructureFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + + int NumInfantryPortals = AITAC_GetNumDeployablesNearLocation(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), &StructureFilter); + + if (NumInfantryPortals == 0) { return true; } + + if (AITAC_GetNumDeadPlayersOnTeam(pBot->Player->GetTeam()) == 0) { return true; } + + return false; +} + +const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPlayer* CommanderBot, const Vector SearchLocation) +{ + AvHTeamNumber CommanderTeam = CommanderBot->Player->GetTeam(); + + const AvHAIHiveDefinition* Result = nullptr; + float MinDist = 0.0f; + + const vector Hives = AITAC_GetAllHives(); + + for (auto it = Hives.begin(); it != Hives.end(); it++) + { + const AvHAIHiveDefinition* Hive = &(*it); + + if (Hive->Status != HIVE_STATUS_UNBUILT) { continue; } + + if (AICOMM_IsHiveFullySecured(CommanderBot, Hive)) { continue; } + + if (AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, Hive->Location, UTIL_MetresToGoldSrcUnits(10.0f)) == nullptr) { continue; } + + 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; + 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; } + } + + } + + float ThisDist = vDist2DSq(Hive->FloorLocation, SearchLocation); + + if (!Result || ThisDist < MinDist) + { + Result = Hive; + MinDist = ThisDist; + } + } + + + return Result; +} + +bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinition* Hive) +{ + AvHTeamNumber CommanderTeam = CommanderBot->Player->GetTeam(); + + bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(CommanderTeam); + + 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 = CommanderTeam; + SearchFilter.ReachabilityTeam = CommanderTeam; + SearchFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + SearchFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + SearchFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + vector HiveStructures = AITAC_FindAllDeployables(Hive->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; + bTurretFactoryElectrified = (Structure->StructureStatusFlags & STRUCTURE_STATUS_ELECTRIFIED); + + SearchFilter.DeployableTypes = STRUCTURE_MARINE_TURRET; + SearchFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(8.0f); + + NumTurrets = AITAC_GetNumDeployablesNearLocation(Structure->Location, &SearchFilter); + + + } + + } + + const AvHAIResourceNode* ResNode = Hive->HiveResNodeRef; + + bool bSecuredResNode = (!ResNode || (ResNode->bIsOccupied && ResNode->OwningTeam == CommanderTeam)); + + bool bShouldElectrifyResNode = (ResNode && bSecuredResNode && CommanderBot->Player->GetResources() > 100 && AITAC_ElectricalResearchIsAvailable(ResNode->ActiveTowerEntity)); + + return ((!bPhaseGatesAvailable || bHasPhaseGate) && bHasTurretFactory && bTurretFactoryElectrified && NumTurrets >= 5 && bSecuredResNode && !bShouldElectrifyResNode); +} \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAICommander.h b/main/source/mod/AIPlayers/AvHAICommander.h new file mode 100644 index 00000000..b1f5cb4b --- /dev/null +++ b/main/source/mod/AIPlayers/AvHAICommander.h @@ -0,0 +1,56 @@ +// +// EvoBot - Neoptolemus' Natural Selection bot, based on Botman's HPB bot template +// +// bot_navigation.h +// +// Handles all bot path finding and movement +// + +#pragma once +#ifndef AVH_AI_COMMANDER_H +#define AVH_AI_COMMANDER_H + +#include "AvHAIConstants.h" + +bool AICOMM_DeployStructure(AvHAIPlayer* pBot, const AvHAIDeployableStructureType StructureToDeploy, const Vector Location); +bool AICOMM_DeployItem(AvHAIPlayer* pBot, const AvHAIDeployableItemType ItemToDeploy, const Vector Location); +bool AICOMM_UpgradeStructure(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToUpgrade); +bool AICOMM_ResearchTech(AvHAIPlayer* pBot, AvHAIBuildableStructure* StructureToResearch, AvHMessageID Research); +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_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_ClearAction(commander_action* Action); +bool AICOMM_CheckForNextBuildAction(AvHAIPlayer* pBot, commander_action* Action); +bool AICOMM_CheckForNextSupportAction(AvHAIPlayer* pBot); +bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot, commander_action* Action); +void AICOMM_SetDropHealthAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient); +void AICOMM_SetDropAmmoAction(AvHAIPlayer* pBot, commander_action* Action, edict_t* Recipient); +void AICOMM_SetDeployStructureAction(AvHAIPlayer* pBot, commander_action* Action, AvHAIDeployableStructureType StructureToBuild, const Vector Location, bool bIsUrgent); +void AICOMM_SetDeployItemAction(AvHAIPlayer* pBot, commander_action* Action, AvHAIDeployableItemType ItemToBuild, const Vector Location, bool bIsUrgent); + +void AICOMM_CommanderThink(AvHAIPlayer* pBot); + +const AvHAIHiveDefinition* AICOMM_GetEmptyHiveOpportunityNearestLocation(AvHAIPlayer* CommanderBot, const Vector SearchLocation); + +bool AICOMM_BuildInfantryPortal(AvHAIPlayer* pBot, edict_t* CommChair, commander_action* Action); +bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege, commander_action* Action); +bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinition* HiveToSiege, commander_action* Action); + +ai_commander_request* AICOMM_GetExistingRequestForPlayer(AvHAIPlayer* pBot, edict_t* Requestor); +void AICOMM_CheckNewRequests(AvHAIPlayer* pBot); +bool AICOMM_IsRequestValid(ai_commander_request* Request); + +bool AICOMM_IsHiveFullySecured(AvHAIPlayer* CommanderBot, const AvHAIHiveDefinition* Hive); + +bool AICOMM_ShouldCommanderLeaveChair(AvHAIPlayer* pBot); + +const AvHAIResourceNode* AICOMM_GetNearestResourceNodeCapOpportunity(const AvHTeamNumber Team, const Vector SearchLocation); +const AvHAIHiveDefinition* AICOMM_GetHiveSiegeOpportunityNearestLocation(AvHAIPlayer* CommanderBot, const Vector SearchLocation); + + +#endif // AVH_AI_COMMANDER_H \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAIConstants.h b/main/source/mod/AIPlayers/AvHAIConstants.h index 60b0d129..1077c3e9 100644 --- a/main/source/mod/AIPlayers/AvHAIConstants.h +++ b/main/source/mod/AIPlayers/AvHAIConstants.h @@ -90,7 +90,7 @@ typedef enum _AI_REACHABILITY_STATUS AI_REACHABILITY_ONOS = 1u << 3, AI_REACHABILITY_WELDER = 1u << 4, - AI_REACHABILITY_ALL = 0xFFFF + AI_REACHABILITY_ALL = -1 } AvHAIReachabilityStatus; typedef enum @@ -101,8 +101,9 @@ typedef enum STRUCTURE_STATUS_RECYCLING = 1 << 2, STRUCTURE_STATUS_PARASITED = 1 << 3, STRUCTURE_STATUS_UNDERATTACK = 1 << 4, + STRUCTURE_STATUS_RESEARCHING = 1 << 5, - STRUCTURE_STATUS_ALL = 0x7FFFFFFF + STRUCTURE_STATUS_ALL = -1 } AvHAIStructureStatus; typedef enum @@ -134,7 +135,7 @@ typedef enum SEARCH_ALL_ALIEN_STRUCTURES = 0xFC000, SEARCH_ANY_RES_TOWER = (STRUCTURE_MARINE_RESTOWER | STRUCTURE_ALIEN_RESTOWER), - SEARCH_ALL_STRUCTURES = 0x7FFFFFFF + SEARCH_ALL_STRUCTURES = -1 } AvHAIDeployableStructureType; @@ -157,7 +158,7 @@ typedef enum DEPLOYABLE_ITEM_WEAPONS = 0xF80, DEPLOYABLE_ITEM_EQUIPMENT = 0x6, - DEPLOYABLE_ITEM_ALL = 0x7FFFFFFF + DEPLOYABLE_ITEM_ALL = -1 } AvHAIDeployableItemType; // Type of goal the commander wants to achieve @@ -513,6 +514,7 @@ typedef struct _COMMANDER_ACTION CommanderActionType ActionType = ACTION_NONE; // What action to perform (e.g. build, recycle, drop item etc) CommanderActionStep ActionStep = ACTION_STEP_NONE; // Used for multi-stage processes such as selecting a building, issuing recycle command etc. AvHAIDeployableStructureType StructureToBuild = STRUCTURE_NONE; // What structure to build if build action + AvHAIDeployableItemType ItemToPlace = DEPLOYABLE_ITEM_NONE; int NumInstances = 0; int NumDesiredInstances = 0; StructurePurpose ActionPurpose = STRUCTURE_PURPOSE_NONE; @@ -520,18 +522,27 @@ typedef struct _COMMANDER_ACTION Vector DesiredCommanderLocation = g_vecZero; // To perform this action, where does the commander's view need to be? For building, usually directly above location, but could be off to side if obstructed by geometry Vector LastAttemptedCommanderLocation = g_vecZero; // The position of the commander's view at the last action attempt Vector LastAttemptedCommanderAngle = g_vecZero; // The click angle of the last action attempt - int AssignedPlayer = -1; // Which player index is assigned to perform the action (e.g. build structure)? Will send orders to that player (move here, build this structure etc.) + int AssignedPlayer = 0; // Which player index is assigned to perform the action (e.g. build structure)? Will send orders to that player (move here, build this structure etc.) edict_t* StructureOrItem = nullptr; // Reference the structure edict. If a structure has been successfully placed but not yet fully built, it will be referenced here edict_t* ActionTarget = nullptr; // Mostly used for dropping health packs and ammo for players where the drop location might be moving around bool bHasAttemptedAction = false; // Has the commander tried placing a structure or item at the build location? If so, and it didn't appear, will try to adjust view around until it works float StructureBuildAttemptTime = 0.0f; // When the commander tried placing a structure. Commander will wait a short while to confirm if the building appeared or if it should try again int NumActionAttempts = 0; // Commander will give up after a certain number of attempts to place structure/item - AvHTechID ResearchId = TECH_NULL; // What research to perform if research action + AvHMessageID ResearchId = MESSAGE_NULL; // What research to perform if research action bool bIsAwaitingBuildLink = false; // The AI has tried placing a structure or item and is waiting to confirm it worked or not bool bIsActionUrgent = false; } commander_action; +typedef struct _AI_COMMANDER_REQUEST +{ + bool bNewRequest = false; // Is this a new request just come in? + edict_t* Requestor = nullptr; // Who sent the request? + AvHMessageID RequestType = MESSAGE_NULL; // What did they request? + bool bResponded = false; // Have we already responded to this request? + float RequestTime = 0.0f; // When the request came in +} ai_commander_request; + typedef struct AVH_AI_PLAYER { AvHPlayer* Player = nullptr; @@ -588,6 +599,8 @@ typedef struct AVH_AI_PLAYER commander_action RecycleAction; commander_action* CurrentAction; + vector ActiveRequests; + float next_commander_action_time = 0.0f; bot_msg ChatMessages[5]; // Bot can have up to 5 chat messages pending diff --git a/main/source/mod/AIPlayers/AvHAINavigation.cpp b/main/source/mod/AIPlayers/AvHAINavigation.cpp index 9089282e..d49d12c7 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.cpp +++ b/main/source/mod/AIPlayers/AvHAINavigation.cpp @@ -2679,7 +2679,7 @@ void CheckAndHandleBreakableObstruction(AvHAIPlayer* pBot, const Vector MoveFrom Vector ClosestPoint = UTIL_GetClosestPointOnEntityToLocation(pBot->Edict->v.origin, BlockingBreakableEdict); - AvHAIWeapon DesiredWeapon = UTIL_GetBotPrimaryWeapon(pBot); + AvHAIWeapon DesiredWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); if (IsPlayerMarine(pBot->Player)) { @@ -2716,7 +2716,7 @@ void CheckAndHandleBreakableObstruction(AvHAIPlayer* pBot, const Vector MoveFrom pBot->DesiredMoveWeapon = DesiredWeapon; - if (GetBotCurrentWeapon(pBot) == DesiredWeapon) + if (GetPlayerCurrentWeapon(pBot->Player) == DesiredWeapon) { pBot->Button |= IN_ATTACK; } @@ -3757,7 +3757,6 @@ bool IsBotOffPath(const AvHAIPlayer* pBot) return (vEquals(PointOnPath, MoveTo, 2.0f) && !IsPlayerClimbingWall(pBot->Edict) && pBot->CollisionHullTopLocation.z < MoveTo.z); } - // Give us a chance to land before deciding we're off the path if (!pBot->BotNavInfo.IsOnGround) { return false; } @@ -3845,7 +3844,7 @@ void BlinkClimbMove(AvHAIPlayer* pBot, const Vector StartPoint, const Vector End pBot->DesiredMoveWeapon = WEAPON_FADE_BLINK; // Wait until we have blink equipped before proceeding - if (GetBotCurrentWeapon(pBot) != WEAPON_FADE_BLINK) { return; } + if (GetPlayerCurrentWeapon(pBot->Player) != WEAPON_FADE_BLINK) { return; } // Only blink if we're below the target climb height if (pEdict->v.origin.z < RequiredClimbHeight) diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.cpp b/main/source/mod/AIPlayers/AvHAIPlayer.cpp index 00a82df8..479603ff 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayer.cpp @@ -8,6 +8,7 @@ #include "AvHAIWeaponHelper.h" #include "AvHAITactical.h" #include "AvHAITask.h" +#include "AvHAICommander.h" #include "../AvHMessage.h" @@ -134,7 +135,7 @@ void BotLookAt(AvHAIPlayer* pBot, edict_t* target) Vector TargetVelocity = (TrackedEnemyRef) ? TrackedEnemyRef->LastSeenVelocity : pBot->LookTarget->v.velocity; Vector TargetLocation = (TrackedEnemyRef) ? TrackedEnemyRef->LastSeenLocation : UTIL_GetCentreOfEntity(pBot->LookTarget); - AvHAIWeapon CurrentWeapon = GetBotCurrentWeapon(pBot); + AvHAIWeapon CurrentWeapon = GetPlayerCurrentWeapon(pBot->Player); Vector NewLoc = UTIL_GetAimLocationToLeadTarget(pBot->CurrentEyePosition, TargetLocation, TargetVelocity, GetProjectileVelocityForWeapon(CurrentWeapon)); @@ -196,9 +197,9 @@ bool BotUseObject(AvHAIPlayer* pBot, edict_t* Target, bool bContinuous) return false; } -AvHAIWeapon GetBotCurrentWeapon(const AvHAIPlayer* pBot) +AvHAIWeapon GetPlayerCurrentWeapon(const AvHPlayer* Player) { - AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(pBot->Player->m_pActiveItem); + AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(Player->m_pActiveItem); if (theBasePlayerWeapon) { @@ -248,7 +249,7 @@ void BotLeap(AvHAIPlayer* pBot, const Vector TargetLocation) AvHAIWeapon LeapWeapon = (IsPlayerSkulk(pBot->Edict)) ? WEAPON_SKULK_LEAP : WEAPON_FADE_BLINK; - if (GetBotCurrentWeapon(pBot) != LeapWeapon) + if (GetPlayerCurrentWeapon(pBot->Player) != LeapWeapon) { pBot->DesiredMoveWeapon = LeapWeapon; return; @@ -415,11 +416,11 @@ void BotReloadWeapons(AvHAIPlayer* pBot) if (gpGlobals->time - pBot->LastCombatTime > 5.0f) { - AvHAIWeapon PrimaryWeapon = UTIL_GetBotPrimaryWeapon(pBot); + AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); AvHAIWeapon SecondaryWeapon = GetBotMarineSecondaryWeapon(pBot); - AvHAIWeapon CurrentWeapon = GetBotCurrentWeapon(pBot); + AvHAIWeapon CurrentWeapon = GetPlayerCurrentWeapon(pBot->Player); - if (WeaponCanBeReloaded(PrimaryWeapon) && BotGetPrimaryWeaponClipAmmo(pBot) < BotGetPrimaryWeaponMaxClipSize(pBot) && BotGetPrimaryWeaponAmmoReserve(pBot) > 0) + if (WeaponCanBeReloaded(PrimaryWeapon) && UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) < UTIL_GetPlayerPrimaryWeaponMaxClipSize(pBot->Player) && UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) { pBot->DesiredCombatWeapon = PrimaryWeapon; @@ -590,7 +591,7 @@ void BotShootTarget(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, edict_t* Target { if (FNullEnt(Target) || (Target->v.deadflag != DEAD_NO)) { return; } - AvHAIWeapon CurrentWeapon = GetBotCurrentWeapon(pBot); + AvHAIWeapon CurrentWeapon = GetPlayerCurrentWeapon(pBot->Player); pBot->DesiredCombatWeapon = AttackWeapon; @@ -686,15 +687,15 @@ void BotShootTarget(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, edict_t* Target if (WeaponCanBeReloaded(CurrentWeapon)) { - bool bShouldReload = (BotGetCurrentWeaponReserveAmmo(pBot) > 0); + bool bShouldReload = (GetPlayerCurrentWeaponReserveAmmo(pBot->Player) > 0); if (CurrentWeapon == WEAPON_MARINE_SHOTGUN && IsEdictStructure(Target)) { - bShouldReload = bShouldReload && ((float)BotGetCurrentWeaponClipAmmo(pBot) / (float)BotGetCurrentWeaponMaxClipAmmo(pBot) < 0.5f); + bShouldReload = bShouldReload && ((float)GetPlayerCurrentWeaponClipAmmo(pBot->Player) / (float)GetPlayerCurrentWeaponMaxClipAmmo(pBot->Player) < 0.5f); } else { - bShouldReload = bShouldReload && BotGetCurrentWeaponClipAmmo(pBot) == 0; + bShouldReload = bShouldReload && GetPlayerCurrentWeaponClipAmmo(pBot->Player) == 0; } if (bShouldReload) @@ -790,7 +791,7 @@ void BotShootLocation(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, const Vector { if (vIsZero(TargetLocation)) { return; } - AvHAIWeapon CurrentWeapon = GetBotCurrentWeapon(pBot); + AvHAIWeapon CurrentWeapon = GetPlayerCurrentWeapon(pBot->Player); pBot->DesiredCombatWeapon = AttackWeapon; @@ -881,15 +882,15 @@ void BotShootLocation(AvHAIPlayer* pBot, AvHAIWeapon AttackWeapon, const Vector if (WeaponCanBeReloaded(CurrentWeapon)) { - bool bShouldReload = (BotGetCurrentWeaponReserveAmmo(pBot) > 0); + bool bShouldReload = (GetPlayerCurrentWeaponReserveAmmo(pBot->Player) > 0); if (CurrentWeapon == WEAPON_MARINE_SHOTGUN) { - bShouldReload = bShouldReload && ((float)BotGetCurrentWeaponClipAmmo(pBot) / (float)BotGetCurrentWeaponMaxClipAmmo(pBot) < 0.5f); + bShouldReload = bShouldReload && ((float)GetPlayerCurrentWeaponClipAmmo(pBot->Player) / (float)GetPlayerCurrentWeaponMaxClipAmmo(pBot->Player) < 0.5f); } else { - bShouldReload = bShouldReload && BotGetCurrentWeaponClipAmmo(pBot) == 0; + bShouldReload = bShouldReload && GetPlayerCurrentWeaponClipAmmo(pBot->Player) == 0; } if (bShouldReload) @@ -1486,6 +1487,11 @@ void StartNewBotFrame(AvHAIPlayer* pBot) } +void CustomThink(AvHAIPlayer* pBot) +{ + AICOMM_CommanderThink(pBot); +} + void DroneThink(AvHAIPlayer* pBot) { AITASK_BotUpdateAndClearTasks(pBot); @@ -1548,7 +1554,7 @@ void BotSwitchToWeapon(AvHAIPlayer* pBot, AvHAIWeapon NewWeaponSlot) bool ShouldBotThink(AvHAIPlayer* pBot) { - return IsPlayerActiveInGame(pBot->Edict) && !IsPlayerGestating(pBot->Edict); + return (IsPlayerActiveInGame(pBot->Edict) || IsPlayerCommander(pBot->Edict)) && !IsPlayerGestating(pBot->Edict); } void BotResumePlay(AvHAIPlayer* pBot) @@ -1583,4 +1589,14 @@ void UpdateCommanderOrders(AvHAIPlayer* pBot) } } } -} \ No newline at end of file +} + +void BotStopCommanderMode(AvHAIPlayer* pBot) +{ + // Thanks EterniumDev (Alien) for logic to allow commander AI to leave the chair and build structures when needed + + if (IsPlayerCommander(pBot->Edict)) + { + CLIENT_COMMAND(pBot->Edict, "stopcommandermode"); + } +} diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.h b/main/source/mod/AIPlayers/AvHAIPlayer.h index 142bbb32..67d2b9bd 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.h +++ b/main/source/mod/AIPlayers/AvHAIPlayer.h @@ -55,6 +55,7 @@ void StartNewBotFrame(AvHAIPlayer* pBot); void TestNavThink(AvHAIPlayer* pBot); void DroneThink(AvHAIPlayer* pBot); +void CustomThink(AvHAIPlayer* pBot); void BotSwitchToWeapon(AvHAIPlayer* pBot, AvHAIWeapon NewWeaponSlot); @@ -64,4 +65,6 @@ void BotResumePlay(AvHAIPlayer* pBot); void UpdateCommanderOrders(AvHAIPlayer* pBot); +void BotStopCommanderMode(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 ca415aea..10422be0 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp @@ -547,13 +547,13 @@ void AIMGR_UpdateAIPlayers() UpdateBotChat(bot); - DroneThink(bot); + CustomThink(bot); AIDEBUG_DrawPath(DebugPath, 0.0f); AvHAIWeapon DesiredWeapon = (bot->DesiredMoveWeapon != WEAPON_NONE) ? bot->DesiredMoveWeapon : bot->DesiredCombatWeapon; - if (DesiredWeapon != WEAPON_NONE && GetBotCurrentWeapon(bot) != DesiredWeapon) + if (DesiredWeapon != WEAPON_NONE && GetPlayerCurrentWeapon(bot->Player) != DesiredWeapon) { BotSwitchToWeapon(bot, DesiredWeapon); } diff --git a/main/source/mod/AIPlayers/AvHAITactical.cpp b/main/source/mod/AIPlayers/AvHAITactical.cpp index 111815e9..61b869e1 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.cpp +++ b/main/source/mod/AIPlayers/AvHAITactical.cpp @@ -223,6 +223,7 @@ AvHAIBuildableStructure* AITAC_FindClosestDeployableToLocation(const Vector& Loc { if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; } if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; } + if (!(it.second.StructureType & Filter->DeployableTypes)) { continue; } unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags); @@ -233,15 +234,12 @@ AvHAIBuildableStructure* AITAC_FindClosestDeployableToLocation(const Vector& Loc if (Filter->ReachabilityFlags != AI_REACHABILITY_NONE && !(StructureReachabilityFlags & Filter->ReachabilityFlags)) { continue; } - if (it.second.StructureType & Filter->DeployableTypes) - { - float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location); + float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location); - if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq) && (!Result || DistSq < CurrMinDist)) - { - Result = &it.second; - CurrMinDist = DistSq; - } + if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq) && (!Result || DistSq < CurrMinDist)) + { + Result = &it.second; + CurrMinDist = DistSq; } } } @@ -252,6 +250,7 @@ AvHAIBuildableStructure* AITAC_FindClosestDeployableToLocation(const Vector& Loc { if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; } if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; } + if (!(it.second.StructureType & Filter->DeployableTypes)) { continue; } unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags); @@ -262,15 +261,83 @@ AvHAIBuildableStructure* AITAC_FindClosestDeployableToLocation(const Vector& Loc if (Filter->ReachabilityFlags != AI_REACHABILITY_NONE && !(StructureReachabilityFlags & Filter->ReachabilityFlags)) { continue; } - if (it.second.StructureType & Filter->DeployableTypes) - { - float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location); + float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location); - if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq) && (!Result || DistSq < CurrMinDist)) - { - Result = &it.second; - CurrMinDist = DistSq; - } + if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq) && (!Result || DistSq < CurrMinDist)) + { + Result = &it.second; + CurrMinDist = DistSq; + } + } + } + + return Result; +} + +AvHAIBuildableStructure* AITAC_FindFurthestDeployableFromLocation(const Vector& Location, const DeployableSearchFilter* Filter) +{ + AvHTeamNumber TeamA = GetGameRules()->GetTeamANumber(); + AvHTeamNumber TeamB = GetGameRules()->GetTeamBNumber(); + + AvHAIBuildableStructure* Result = NULL; + float CurrMinDist = 0.0f; + + float MinDistSq = sqrf(Filter->MinSearchRadius); + float MaxDistSq = sqrf(Filter->MaxSearchRadius); + + bool bUseMinDist = MinDistSq > 0.1f; + bool bUseMaxDist = MaxDistSq > 0.1f; + + if (Filter->DeployableTeam == TeamA || Filter->DeployableTeam == TEAM_IND) + { + for (auto& it : TeamAStructureMap) + { + if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; } + if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; } + if (!(it.second.StructureType & Filter->DeployableTypes)) { continue; } + + unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags); + + if (Filter->ReachabilityTeam != TEAM_IND) + { + StructureReachabilityFlags = (Filter->ReachabilityTeam == TeamA) ? it.second.TeamAReachabilityFlags : it.second.TeamBReachabilityFlags; + } + + if (Filter->ReachabilityFlags != AI_REACHABILITY_NONE && !(StructureReachabilityFlags & Filter->ReachabilityFlags)) { continue; } + + float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location); + + if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq) && DistSq > CurrMinDist) + { + Result = &it.second; + CurrMinDist = DistSq; + } + } + } + + if (Filter->DeployableTeam == TeamB || Filter->DeployableTeam == TEAM_IND) + { + for (auto& it : TeamBStructureMap) + { + if (it.second.StructureStatusFlags & Filter->ExcludeStatusFlags) { continue; } + if ((it.second.StructureStatusFlags & Filter->IncludeStatusFlags) != Filter->IncludeStatusFlags) { continue; } + if (!(it.second.StructureType & Filter->DeployableTypes)) { continue; } + + unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags); + + if (Filter->ReachabilityTeam != TEAM_IND) + { + StructureReachabilityFlags = (Filter->ReachabilityTeam == TeamA) ? it.second.TeamAReachabilityFlags : it.second.TeamBReachabilityFlags; + } + + if (Filter->ReachabilityFlags != AI_REACHABILITY_NONE && !(StructureReachabilityFlags & Filter->ReachabilityFlags)) { continue; } + + float DistSq = (Filter->bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location); + + if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq) && DistSq > CurrMinDist) + { + Result = &it.second; + CurrMinDist = DistSq; } } } @@ -326,6 +393,75 @@ AvHAIDroppedItem* AITAC_FindClosestItemToLocation(const Vector& Location, const return Result; } +int AITAC_GetNumItemsInLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance) +{ + int Result = 0; + + float MinDistSq = sqrf(MinRadius); + float MaxDistSq = sqrf(MaxRadius); + + bool bUseMinDist = MinDistSq > 0.1f; + bool bUseMaxDist = MaxDistSq > 0.1f; + + for (auto& it : MarineDroppedItemMap) + { + if (it.second.ItemType != ItemType) { continue; } + + unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags); + + if (SearchingTeam != TEAM_IND) + { + StructureReachabilityFlags = (SearchingTeam == GetGameRules()->GetTeamANumber()) ? it.second.TeamAReachabilityFlags : it.second.TeamBReachabilityFlags; + } + + if (ReachabilityFlags != AI_REACHABILITY_NONE && !(StructureReachabilityFlags & ReachabilityFlags)) { continue; } + + float DistSq = (bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location); + + if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq)) + { + Result++; + } + + } + + return Result; +} + +bool AITAC_ItemExistsInLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance) +{ + + float MinDistSq = sqrf(MinRadius); + float MaxDistSq = sqrf(MaxRadius); + + bool bUseMinDist = MinDistSq > 0.1f; + bool bUseMaxDist = MaxDistSq > 0.1f; + + for (auto& it : MarineDroppedItemMap) + { + unsigned int StructureReachabilityFlags = (it.second.TeamAReachabilityFlags | it.second.TeamBReachabilityFlags); + + if (SearchingTeam != TEAM_IND) + { + StructureReachabilityFlags = (SearchingTeam == GetGameRules()->GetTeamANumber()) ? it.second.TeamAReachabilityFlags : it.second.TeamBReachabilityFlags; + } + + if (ReachabilityFlags != AI_REACHABILITY_NONE && !(StructureReachabilityFlags & ReachabilityFlags)) { continue; } + + if (it.second.ItemType != ItemType) { continue; } + + float DistSq = (bConsiderPhaseDistance) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(it.second.Location, Location)) : vDist2DSq(it.second.Location, Location); + + if ((!bUseMinDist || DistSq >= MinDistSq) && (!bUseMaxDist || DistSq <= MaxDistSq)) + { + return true; + } + + } + + return false; +} + AvHAIBuildableStructure* AITAC_GetDeployableRefFromEdict(const edict_t* Structure) { if (FNullEnt(Structure)) { return nullptr; } @@ -1470,7 +1606,6 @@ void AITAC_UpdateBuildableStructure(CBaseEntity* Structure) { BuildingMap[EntIndex].EntityRef = BaseBuildable; BuildingMap[EntIndex].edict = BuildingEdict; - BuildingMap[EntIndex].StructureType = StructureType; BuildingMap[EntIndex].OffMeshConnections.clear(); @@ -1494,6 +1629,8 @@ void AITAC_UpdateBuildableStructure(CBaseEntity* Structure) AITAC_OnStructureCreated(&BuildingMap[EntIndex]); } + BuildingMap[EntIndex].StructureType = StructureType; + if (vIsZero(BuildingMap[EntIndex].Location) || !vEquals(BaseBuildable->pev->origin, BuildingMap[EntIndex].Location, 5.0f)) { AITAC_RefreshReachabilityForStructure(&BuildingMap[EntIndex]); @@ -1530,6 +1667,11 @@ void AITAC_UpdateBuildableStructure(CBaseEntity* Structure) NewFlags |= STRUCTURE_STATUS_RECYCLING; } + if (BaseBuildable->GetIsResearching()) + { + NewFlags |= STRUCTURE_STATUS_RESEARCHING; + } + float NewHealthPercent = (BuildingEdict->v.health / BuildingEdict->v.max_health); if (NewHealthPercent < BuildingMap[EntIndex].healthPercent) @@ -2030,6 +2172,40 @@ bool UTIL_StructureIsResearching(edict_t* Structure, const AvHMessageID Research return (StructureRef && StructureRef->GetIsResearching() && Structure->v.iuser2 == (int)Research); } +bool AITAC_MarineResearchIsAvailable(const AvHTeamNumber Team, const AvHMessageID Research) +{ + AvHTeam* PlayerTeam = GetGameRules()->GetTeam(Team); + + if (!PlayerTeam) { return false; } + + AvHMessageID Message = Research; + + return PlayerTeam->GetResearchManager().GetIsMessageAvailable(Message); + +} + +bool AITAC_ElectricalResearchIsAvailable(edict_t* Structure) +{ + if (FNullEnt(Structure)) { return false; } + + if (UTIL_IsStructureElectrified(Structure)) { return false; } + + AvHAIDeployableStructureType StructureTypeToElectrify = GetStructureTypeFromEdict(Structure); + AvHAIDeployableStructureType ElectrifyableStructureTypes = (AvHAIDeployableStructureType)(STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY | STRUCTURE_MARINE_RESTOWER); + + if (!(ElectrifyableStructureTypes & StructureTypeToElectrify)) { return false; } + + DeployableSearchFilter TFFilter; + TFFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY; + TFFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + TFFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + TFFilter.DeployableTeam = (AvHTeamNumber)Structure->v.team; + + if (!AITAC_DeployableExistsAtLocation(AITAC_GetCommChairLocation((AvHTeamNumber)Structure->v.team), &TFFilter)) { return false; } + + return (Structure->v.iuser4 & MASK_UPGRADE_1); +} + AvHAIHiveDefinition* AITAC_GetHiveFromEdict(const edict_t* Edict) { for (auto it = Hives.begin(); it != Hives.end(); it++) @@ -2497,6 +2673,41 @@ AvHMessageID UTIL_StructureTypeToImpulseCommand(const AvHAIDeployableStructureTy return MESSAGE_NULL; } +AvHMessageID UTIL_ItemTypeToImpulseCommand(const AvHAIDeployableItemType ItemType) +{ + switch (ItemType) + { + + case DEPLOYABLE_ITEM_HEAVYARMOUR: + return BUILD_HEAVY; + case DEPLOYABLE_ITEM_JETPACK: + return BUILD_JETPACK; + case DEPLOYABLE_ITEM_CATALYSTS: + return BUILD_CAT; + case DEPLOYABLE_ITEM_SCAN: + return BUILD_SCAN; + case DEPLOYABLE_ITEM_HEALTHPACK: + return BUILD_HEALTH; + case DEPLOYABLE_ITEM_AMMO: + return BUILD_AMMO; + case DEPLOYABLE_ITEM_MINES: + return BUILD_MINES; + case DEPLOYABLE_ITEM_WELDER: + return BUILD_WELDER; + case DEPLOYABLE_ITEM_SHOTGUN: + return BUILD_SHOTGUN; + case DEPLOYABLE_ITEM_HMG: + return BUILD_HMG; + case DEPLOYABLE_ITEM_GRENADELAUNCHER: + return BUILD_GRENADE_GUN; + default: + return MESSAGE_NULL; + + } + + return MESSAGE_NULL; +} + edict_t* AITAC_GetClosestPlayerOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer) { float distSq = sqrf(SearchRadius); @@ -2663,7 +2874,7 @@ AvHAIDeployableStructureType UTIL_GetChamberTypeForHiveTech(AvHMessageID HiveTec return STRUCTURE_NONE; } -bool UTIL_ResearchIsComplete(const AvHTeamNumber Team, const AvHTechID Research) +bool AITAC_ResearchIsComplete(const AvHTeamNumber Team, const AvHTechID Research) { AvHTeam* TeamRef = GetGameRules()->GetTeam(Team); @@ -2672,4 +2883,200 @@ bool UTIL_ResearchIsComplete(const AvHTeamNumber Team, const AvHTechID Research) AvHResearchManager ResearchManager = TeamRef->GetResearchManager(); return ResearchManager.GetTechNodes().GetIsTechResearched(Research); +} + +bool AITAC_PhaseGatesAvailable(const AvHTeamNumber Team) +{ + DeployableSearchFilter ObsFilter; + ObsFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + ObsFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + ObsFilter.DeployableTypes = STRUCTURE_MARINE_OBSERVATORY; + ObsFilter.DeployableTeam = Team; + + bool bObsExists = AITAC_DeployableExistsAtLocation(AITAC_GetTeamStartingLocation(Team), &ObsFilter); + + return bObsExists && AITAC_ResearchIsComplete(Team, TECH_RESEARCH_PHASETECH); +} + +int AITAC_GetNumDeadPlayersOnTeam(const AvHTeamNumber Team) +{ + AvHTeam* TeamRef = GetGameRules()->GetTeam(Team); + + if (!TeamRef) { return 0; } + + return TeamRef->GetPlayerCount(true); +} + +bool AITAC_StructureCanBeUpgraded(edict_t* Structure) +{ + // We can't upgrade a structure if it's not built, destroyed, or already doing something + if (FNullEnt(Structure) + || Structure->v.deadflag != DEAD_NO + || !UTIL_StructureIsFullyBuilt(Structure) + || UTIL_StructureIsRecycling(Structure) + || UTIL_StructureIsResearching(Structure) + || UTIL_StructureIsUpgrading(Structure)) + { + return false; + } + + return (GetStructureTypeFromEdict(Structure) == STRUCTURE_MARINE_ARMOURY || GetStructureTypeFromEdict(Structure) == STRUCTURE_MARINE_TURRETFACTORY); +} + +const AvHAIHiveDefinition* AITAC_GetNearestHiveUnderActiveSiege(AvHTeamNumber SiegingTeam, const Vector SearchLocation) +{ + AvHAIHiveDefinition* Result = nullptr; + float MinDist = 0.0f; + + for (auto it = Hives.begin(); it != Hives.end(); it++) + { + if (it->Status == HIVE_STATUS_UNBUILT) { continue; } + + DeployableSearchFilter SiegeFilter; + SiegeFilter.DeployableTypes = STRUCTURE_MARINE_ADVTURRETFACTORY; + SiegeFilter.DeployableTeam = SiegingTeam; + SiegeFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(20.0f); + SiegeFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + SiegeFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + AvHAIBuildableStructure* AdvTF = AITAC_FindClosestDeployableToLocation(it->Location, &SiegeFilter); + + if (!AdvTF) { continue; } + + SiegeFilter.DeployableTypes = STRUCTURE_MARINE_SIEGETURRET; + SiegeFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f); + + bool bSTExists = AITAC_DeployableExistsAtLocation(AdvTF->Location, &SiegeFilter); + + if (bSTExists) + { + float ThisDist = vDist2DSq(SearchLocation, it->Location); + + if (!Result || ThisDist < MinDist) + { + Result = &(*it); + MinDist = ThisDist; + } + } + + } + + return Result; +} + +edict_t* AITAC_GetMarineEligibleToBuildSiege(AvHTeamNumber Team, const AvHAIHiveDefinition* Hive) +{ + if (!Hive) { return nullptr; } + + edict_t* Result = nullptr; + + vector TeamPlayers = AITAC_GetAllPlayersOnTeam(Team); + + float MinDist = 0.0f; + + for (auto it = TeamPlayers.begin(); it != TeamPlayers.end(); it++) + { + edict_t* PlayerEdict = (*it)->edict(); + + // Find a player who is alive and not currently sighted by the enemy + if (!IsPlayerActiveInGame(PlayerEdict) || (PlayerEdict->v.iuser4 & MASK_VIS_SIGHTED)) { continue; } + + float ThisDist = vDist2DSq(PlayerEdict->v.origin, Hive->Location); + + if (ThisDist < sqrf(UTIL_MetresToGoldSrcUnits(25.0f)) && !UTIL_QuickTrace(PlayerEdict, GetPlayerEyePosition(PlayerEdict), Hive->Location)) + { + if (FNullEnt(Result) || ThisDist < MinDist) + { + Result = PlayerEdict; + MinDist = ThisDist; + } + } + } + + return Result; +} + +edict_t* AITAC_GetNearestHiddenPlayerInLocation(AvHTeamNumber Team, const Vector Location, const float MaxRadius) +{ + edict_t* Result = nullptr; + float MaxRadiusSq = sqrf(MaxRadius); + + vector TeamPlayers = AITAC_GetAllPlayersOnTeam(Team); + + float MinDist = 0.0f; + + for (auto it = TeamPlayers.begin(); it != TeamPlayers.end(); it++) + { + edict_t* PlayerEdict = (*it)->edict(); + + // Find a player who is alive and not currently sighted by the enemy + if (!IsPlayerActiveInGame(PlayerEdict) || (PlayerEdict->v.iuser4 & MASK_VIS_SIGHTED)) { continue; } + + float ThisDist = vDist2DSq(PlayerEdict->v.origin, Location); + + if (ThisDist < MaxRadiusSq) + { + if (FNullEnt(Result) || ThisDist < MinDist) + { + Result = PlayerEdict; + MinDist = ThisDist; + } + } + } + + 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() +{ + return ResourceNodes; +} + +const vector& AITAC_GetAllHives() +{ + return Hives; +} + +bool AITAC_AnyPlayerOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius) +{ + float distSq = sqrf(SearchRadius); + + vector Players = AITAC_GetAllPlayersOnTeam(Team); + + for (auto it = Players.begin(); it != Players.end(); it++) + { + edict_t* PlayerRef = (*it)->edict(); + + if (!IsPlayerActiveInGame(PlayerRef)) { continue; } + + if (vDist2DSq(PlayerRef->v.origin, Location) <= distSq && UTIL_QuickTrace(PlayerRef, GetPlayerEyePosition(PlayerRef), Location)) + { + return true; + } + } + + return false; } \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAITactical.h b/main/source/mod/AIPlayers/AvHAITactical.h index bc86782d..0b892c84 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.h +++ b/main/source/mod/AIPlayers/AvHAITactical.h @@ -23,6 +23,7 @@ static const float item_inventory_refresh_rate = 0.1f; bool AITAC_DeployableExistsAtLocation(const Vector& Location, const DeployableSearchFilter* Filter); std::vector AITAC_FindAllDeployables(const Vector& Location, const DeployableSearchFilter* Filter); AvHAIBuildableStructure* AITAC_FindClosestDeployableToLocation(const Vector& Location, const DeployableSearchFilter* Filter); +AvHAIBuildableStructure* AITAC_FindFurthestDeployableFromLocation(const Vector& Location, const DeployableSearchFilter* Filter); 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); @@ -57,6 +58,8 @@ Vector AITAC_GetTeamStartingLocation(AvHTeamNumber Team); AvHAIResourceNode* AITAC_GetRandomResourceNode(AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags); AvHAIDroppedItem* AITAC_FindClosestItemToLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance); +bool AITAC_ItemExistsInLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance); +int AITAC_GetNumItemsInLocation(const Vector& Location, const AvHAIDeployableItemType ItemType, AvHTeamNumber SearchingTeam, const unsigned int ReachabilityFlags, float MinRadius, float MaxRadius, bool bConsiderPhaseDistance); AvHAIDroppedItem* AITAC_GetDroppedItemRefFromEdict(edict_t* ItemEdict); @@ -67,6 +70,7 @@ int AITAC_GetNumHives(); void AITAC_OnNavMeshModified(); AvHMessageID UTIL_StructureTypeToImpulseCommand(const AvHAIDeployableStructureType StructureType); +AvHMessageID UTIL_ItemTypeToImpulseCommand(const AvHAIDeployableItemType ItemType); edict_t* AITAC_GetClosestPlayerOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer); bool AITAC_AnyPlayerOnTeamHasLOSToLocation(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer); @@ -94,6 +98,7 @@ unsigned char UTIL_GetAreaForObstruction(AvHAIDeployableStructureType StructureT bool UTIL_IsStructureElectrified(edict_t* Structure); bool UTIL_StructureIsFullyBuilt(edict_t* Structure); bool UTIL_StructureIsRecycling(edict_t* Structure); +bool AITAC_StructureCanBeUpgraded(edict_t* Structure); AvHAIHiveDefinition* AITAC_GetHiveFromEdict(const edict_t* Edict); @@ -118,6 +123,9 @@ bool UTIL_StructureIsResearching(edict_t* Structure); bool UTIL_StructureIsResearching(edict_t* Structure, const AvHMessageID Research); bool UTIL_StructureIsUpgrading(edict_t* Structure); +bool AITAC_MarineResearchIsAvailable(const AvHTeamNumber Team, const AvHMessageID Research); +bool AITAC_ElectricalResearchIsAvailable(edict_t* Structure); + Vector UTIL_GetNextMinePosition(edict_t* StructureToMine); int UTIL_GetCostOfStructureType(AvHAIDeployableStructureType StructureType); @@ -125,6 +133,21 @@ edict_t* AITAC_GetNearestHumanAtLocation(const AvHTeamNumber Team, const Vector AvHAIDeployableStructureType UTIL_GetChamberTypeForHiveTech(AvHMessageID HiveTech); -bool UTIL_ResearchIsComplete(const AvHTeamNumber Team, const AvHTechID Research); +bool AITAC_ResearchIsComplete(const AvHTeamNumber Team, const AvHTechID Research); + +bool AITAC_PhaseGatesAvailable(const AvHTeamNumber Team); + +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(); +const vector& AITAC_GetAllHives(); + +bool AITAC_AnyPlayerOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius); #endif \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAITask.cpp b/main/source/mod/AIPlayers/AvHAITask.cpp index 74b9800a..2cbd44e2 100644 --- a/main/source/mod/AIPlayers/AvHAITask.cpp +++ b/main/source/mod/AIPlayers/AvHAITask.cpp @@ -148,7 +148,7 @@ bool AITASK_IsTaskUrgent(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) switch (Task->TaskType) { case TASK_GET_AMMO: - return (BotGetPrimaryWeaponAmmoReserve(pBot) == 0); + return (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) == 0); case TASK_GET_HEALTH: return (IsPlayerMarine(pBot->Edict)) ? (pBot->Edict->v.health < 50.0f) : (GetPlayerOverallHealthPercent(pBot->Edict) < 50.0f); case TASK_ATTACK: @@ -157,7 +157,7 @@ bool AITASK_IsTaskUrgent(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) case TASK_WELD: return false; case TASK_RESUPPLY: - return (pBot->Edict->v.health < 50.0f) || (BotGetPrimaryWeaponAmmoReserve(pBot) == 0); + return (pBot->Edict->v.health < 50.0f) || (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) == 0); case TASK_MOVE: return AITASK_IsMoveTaskUrgent(pBot, Task); case TASK_BUILD: @@ -420,7 +420,7 @@ bool AITASK_IsAmmoPickupTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (!UTIL_IsDroppedItemStillReachable(pBot, Task->TaskTarget)) { return false; } - return (vDist2DSq(pBot->Edict->v.origin, Task->TaskTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(20.0f))) && (BotGetPrimaryWeaponAmmoReserve(pBot) < BotGetPrimaryWeaponMaxAmmoReserve(pBot)); + return (vDist2DSq(pBot->Edict->v.origin, Task->TaskTarget->v.origin) < sqrf(UTIL_MetresToGoldSrcUnits(20.0f))) && (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryMaxAmmoReserve(pBot->Player)); } bool AITASK_IsHealthPickupTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) @@ -475,7 +475,7 @@ bool AITASK_IsAttackTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { if (StructureType == STRUCTURE_ALIEN_HIVE || StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER) { - if (BotGetPrimaryWeaponClipAmmo(pBot) <= 0 && BotGetPrimaryWeaponAmmoReserve(pBot) <= 0) + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) <= 0 && UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) <= 0) { return false; } @@ -499,7 +499,7 @@ bool AITASK_IsResupplyTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (!UTIL_StructureIsFullyBuilt(Task->TaskTarget) || UTIL_StructureIsRecycling(Task->TaskTarget)) { return false; } return ((pBot->Edict->v.health < pBot->Edict->v.max_health) - || (BotGetPrimaryWeaponAmmoReserve(pBot) < BotGetPrimaryWeaponMaxAmmoReserve(pBot)) + || (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryMaxAmmoReserve(pBot->Player)) || (BotGetSecondaryWeaponAmmoReserve(pBot) < BotGetSecondaryWeaponMaxAmmoReserve(pBot)) ); } @@ -1020,7 +1020,7 @@ void BotProgressPickupTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { if (Task->TaskType == TASK_GET_AMMO) { - pBot->DesiredCombatWeapon = UTIL_GetBotPrimaryWeapon(pBot); + pBot->DesiredCombatWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); } MoveTo(pBot, Task->TaskTarget->v.origin, MOVESTYLE_NORMAL); @@ -1040,11 +1040,11 @@ void BotProgressPickupTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) // Allows bots to drop their current primary weapon to pick up a new weapon if (UTIL_DroppedItemIsPrimaryWeapon(ItemType)) { - AvHAIWeapon CurrentPrimaryWeapon = UTIL_GetBotPrimaryWeapon(pBot); + AvHAIWeapon CurrentPrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); if (CurrentPrimaryWeapon != WEAPON_NONE && CurrentPrimaryWeapon != UTIL_GetWeaponTypeFromEdict(Task->TaskTarget)) { - if (GetBotCurrentWeapon(pBot) != CurrentPrimaryWeapon) + if (GetPlayerCurrentWeapon(pBot->Player) != CurrentPrimaryWeapon) { pBot->DesiredCombatWeapon = CurrentPrimaryWeapon; } @@ -1121,7 +1121,7 @@ void BotProgressMineStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) BotLookAt(pBot, Task->TaskLocation); - if (GetBotCurrentWeapon(pBot) == WEAPON_MARINE_MINES) + if (GetPlayerCurrentWeapon(pBot->Player) == WEAPON_MARINE_MINES) { float LookDot = UTIL_GetDotProduct(UTIL_GetForwardVector(pBot->Edict->v.v_angle), UTIL_GetVectorNormal(Task->TaskLocation - pBot->CurrentEyePosition)); @@ -1410,9 +1410,9 @@ void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) void BotProgressResupplyTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { - if (BotGetPrimaryWeaponAmmoReserve(pBot) < BotGetPrimaryWeaponMaxAmmoReserve(pBot)) + if (UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) < UTIL_GetPlayerPrimaryMaxAmmoReserve(pBot->Player)) { - pBot->DesiredCombatWeapon = UTIL_GetBotPrimaryWeapon(pBot); + pBot->DesiredCombatWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); } else { @@ -1442,9 +1442,9 @@ void MarineProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { edict_t* pEdict = pBot->Edict; - if (BotGetPrimaryWeaponClipAmmo(pBot) > 0 || BotGetPrimaryWeaponAmmoReserve(pBot) > 0) + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) { - pBot->DesiredCombatWeapon = UTIL_GetBotPrimaryWeapon(pBot); + pBot->DesiredCombatWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); } else { @@ -1454,7 +1454,7 @@ void MarineProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) } else { - pBot->DesiredCombatWeapon = UTIL_GetBotPrimaryWeapon(pBot); + pBot->DesiredCombatWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); } } @@ -1656,6 +1656,9 @@ void BotProgressDefendTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) void BotProgressTakeCommandTask(AvHAIPlayer* pBot) { + // Don't take command if we already have a commander + if (pBot->Player->GetCommander()) { return; } + edict_t* CommChair = AITAC_GetCommChair(pBot->Player->GetTeam()); if (!CommChair) { return; } @@ -1775,7 +1778,7 @@ void AlienProgressGetHealthTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (PlayerHasWeapon(pBot->Player, WEAPON_FADE_METABOLIZE)) { pBot->DesiredCombatWeapon = WEAPON_FADE_METABOLIZE; - if (GetBotCurrentWeapon(pBot) == WEAPON_FADE_METABOLIZE) + if (GetPlayerCurrentWeapon(pBot->Player) == WEAPON_FADE_METABOLIZE) { pBot->Button |= IN_ATTACK; } @@ -1812,7 +1815,7 @@ void AlienProgressHealTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { pBot->DesiredCombatWeapon = WEAPON_GORGE_HEALINGSPRAY; BotLookAt(pBot, UTIL_GetCentreOfEntity(Task->TaskTarget)); - if (GetBotCurrentWeapon(pBot) == WEAPON_GORGE_HEALINGSPRAY) + if (GetPlayerCurrentWeapon(pBot->Player) == WEAPON_GORGE_HEALINGSPRAY) { pBot->Button |= IN_ATTACK; } @@ -2086,7 +2089,7 @@ void AlienProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { pBot->DesiredCombatWeapon = AttackWeapon; - if (GetBotCurrentWeapon(pBot) == AttackWeapon) + if (GetPlayerCurrentWeapon(pBot->Player) == AttackWeapon) { BotShootTarget(pBot, pBot->DesiredCombatWeapon, Task->TaskTarget); return; @@ -2281,7 +2284,7 @@ void BotProgressWeldTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) BotLookAt(pBot, vClosestPointOnBB(pBot->CurrentEyePosition, BBMin, BBMax)); pBot->DesiredCombatWeapon = WEAPON_MARINE_WELDER; - if (GetBotCurrentWeapon(pBot) != WEAPON_MARINE_WELDER) + if (GetPlayerCurrentWeapon(pBot->Player) != WEAPON_MARINE_WELDER) { return; } @@ -2322,7 +2325,7 @@ void MarineProgressSecureHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) if (!TF || !(TF->StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { bWaitForBuildingPlacement = true; } - bool bPhaseGatesAvailable = UTIL_ResearchIsComplete(pBot->Player->GetTeam(), TECH_PHASE_GATE); + bool bPhaseGatesAvailable = AITAC_ResearchIsComplete(pBot->Player->GetTeam(), TECH_PHASE_GATE); if (bPhaseGatesAvailable && !bWaitForBuildingPlacement) { @@ -2443,7 +2446,7 @@ void MarineProgressCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { pBot->DesiredCombatWeapon = AttackWeapon; - if (GetBotCurrentWeapon(pBot) == AttackWeapon) + if (GetPlayerCurrentWeapon(pBot->Player) == AttackWeapon) { //BotShootTarget(pBot, pBot->DesiredCombatWeapon, Task->TaskTarget); } diff --git a/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp b/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp index e0c2689c..be15a302 100644 --- a/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp +++ b/main/source/mod/AIPlayers/AvHAIWeaponHelper.cpp @@ -16,9 +16,9 @@ extern nav_mesh NavMeshes[MAX_NAV_MESHES]; // Array of nav meshes. Currently only 3 are used (building, onos, and regular) extern nav_profile BaseNavProfiles[MAX_NAV_PROFILES]; // Array of nav profiles -int BotGetCurrentWeaponClipAmmo(const AvHAIPlayer* pBot) +int GetPlayerCurrentWeaponClipAmmo(const AvHPlayer* Player) { - AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(pBot->Player->m_pActiveItem); + AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(Player->m_pActiveItem); if (theBasePlayerWeapon) { @@ -28,9 +28,9 @@ int BotGetCurrentWeaponClipAmmo(const AvHAIPlayer* pBot) return 0; } -int BotGetCurrentWeaponMaxClipAmmo(const AvHAIPlayer* pBot) +int GetPlayerCurrentWeaponMaxClipAmmo(const AvHPlayer* Player) { - AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(pBot->Player->m_pActiveItem); + AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(Player->m_pActiveItem); if (theBasePlayerWeapon) { @@ -40,13 +40,13 @@ int BotGetCurrentWeaponMaxClipAmmo(const AvHAIPlayer* pBot) return 0; } -int BotGetCurrentWeaponReserveAmmo(const AvHAIPlayer* pBot) +int GetPlayerCurrentWeaponReserveAmmo(const AvHPlayer* Player) { - AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(pBot->Player->m_pActiveItem); + AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(Player->m_pActiveItem); if (theBasePlayerWeapon) { - return pBot->Player->m_rgAmmo[theBasePlayerWeapon->m_iPrimaryAmmoType]; + return Player->m_rgAmmo[theBasePlayerWeapon->m_iPrimaryAmmoType]; } return 0; @@ -165,9 +165,21 @@ void InterruptReload(AvHAIPlayer* pBot) pBot->Button |= IN_ATTACK; } -AvHAIWeapon UTIL_GetBotPrimaryWeapon(const AvHAIPlayer* pBot) +AvHAIWeapon UTIL_GetPlayerPrimaryWeapon(const AvHPlayer* Player) { - AvHBasePlayerWeapon* Weapon = dynamic_cast(pBot->Player->m_rgpPlayerItems[1]); + AvHBasePlayerWeapon* Weapon = dynamic_cast(Player->m_rgpPlayerItems[1]); + + if (Weapon) + { + return (AvHAIWeapon)Weapon->m_iId; + } + + return WEAPON_INVALID; +} + +AvHAIWeapon UTIL_GetPlayerSecondaryWeapon(const AvHPlayer* Player) +{ + AvHBasePlayerWeapon* Weapon = dynamic_cast(Player->m_rgpPlayerItems[2]); if (Weapon) { @@ -206,9 +218,9 @@ AvHAIWeapon GetBotMarineSecondaryWeapon(const AvHAIPlayer* pBot) return WEAPON_INVALID; } -int BotGetPrimaryWeaponMaxAmmoReserve(AvHAIPlayer* pBot) +int UTIL_GetPlayerPrimaryMaxAmmoReserve(AvHPlayer* Player) { - AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(pBot->Player->m_rgpPlayerItems[1]); + AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(Player->m_rgpPlayerItems[1]); if (theBasePlayerWeapon) { @@ -218,13 +230,37 @@ int BotGetPrimaryWeaponMaxAmmoReserve(AvHAIPlayer* pBot) return 0; } -int BotGetPrimaryWeaponAmmoReserve(AvHAIPlayer* pBot) +int UTIL_GetPlayerPrimaryAmmoReserve(AvHPlayer* Player) { - AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(pBot->Player->m_rgpPlayerItems[1]); + AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(Player->m_rgpPlayerItems[1]); if (theBasePlayerWeapon) { - return pBot->Player->m_rgAmmo[theBasePlayerWeapon->m_iPrimaryAmmoType]; + return Player->m_rgAmmo[theBasePlayerWeapon->m_iPrimaryAmmoType]; + } + + return 0; +} + +int UTIL_GetPlayerSecondaryMaxAmmoReserve(AvHPlayer* Player) +{ + AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(Player->m_rgpPlayerItems[2]); + + if (theBasePlayerWeapon) + { + return theBasePlayerWeapon->iMaxAmmo1(); + } + + return 0; +} + +int UTIL_GetPlayerSecondaryAmmoReserve(AvHPlayer* Player) +{ + AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(Player->m_rgpPlayerItems[2]); + + if (theBasePlayerWeapon) + { + return Player->m_rgAmmo[theBasePlayerWeapon->m_iPrimaryAmmoType]; } return 0; @@ -242,9 +278,9 @@ int BotGetSecondaryWeaponAmmoReserve(AvHAIPlayer* pBot) return 0; } -int BotGetPrimaryWeaponClipAmmo(const AvHAIPlayer* pBot) +int UTIL_GetPlayerPrimaryWeaponClipAmmo(const AvHPlayer* Player) { - AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(pBot->Player->m_rgpPlayerItems[1]); + AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(Player->m_rgpPlayerItems[1]); if (theBasePlayerWeapon) { @@ -266,9 +302,33 @@ int BotGetSecondaryWeaponClipAmmo(const AvHAIPlayer* pBot) return 0; } -int BotGetPrimaryWeaponMaxClipSize(const AvHAIPlayer* pBot) +int UTIL_GetPlayerPrimaryWeaponMaxClipSize(const AvHPlayer* Player) { - AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(pBot->Player->m_rgpPlayerItems[1]); + AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(Player->m_rgpPlayerItems[1]); + + if (theBasePlayerWeapon) + { + return theBasePlayerWeapon->iMaxClip(); + } + + return 0; +} + +int UTIL_GetPlayerSecondaryWeaponClipAmmo(const AvHPlayer* Player) +{ + AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(Player->m_rgpPlayerItems[2]); + + if (theBasePlayerWeapon) + { + return theBasePlayerWeapon->m_iClip; + } + + return 0; +} + +int UTIL_GetPlayerSecondaryWeaponMaxClipSize(const AvHPlayer* Player) +{ + AvHBasePlayerWeapon* theBasePlayerWeapon = dynamic_cast(Player->m_rgpPlayerItems[2]); if (theBasePlayerWeapon) { @@ -471,9 +531,9 @@ AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target) if (FNullEnt(target)) { - if (BotGetPrimaryWeaponClipAmmo(pBot) > 0 || BotGetPrimaryWeaponAmmoReserve(pBot) > 0) + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) { - return UTIL_GetBotPrimaryWeapon(pBot); + return UTIL_GetPlayerPrimaryWeapon(pBot->Player); } else if (BotGetSecondaryWeaponClipAmmo(pBot) > 0 || BotGetSecondaryWeaponAmmoReserve(pBot) > 0) { @@ -481,7 +541,7 @@ AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target) } else { - return UTIL_GetBotPrimaryWeapon(pBot); + return UTIL_GetPlayerPrimaryWeapon(pBot->Player); } } @@ -489,9 +549,9 @@ AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target) { float DistFromEnemy = vDist2DSq(pBot->Edict->v.origin, target->v.origin); - if (UTIL_GetBotPrimaryWeapon(pBot) == WEAPON_MARINE_GL) + if (UTIL_GetPlayerPrimaryWeapon(pBot->Player) == WEAPON_MARINE_GL) { - if (BotGetPrimaryWeaponClipAmmo(pBot) > 0 && DistFromEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 && DistFromEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { return WEAPON_MARINE_GL; } @@ -506,7 +566,7 @@ AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target) if (DistFromEnemy <= sqrf(UTIL_MetresToGoldSrcUnits(2.0f))) { - if (BotGetPrimaryWeaponClipAmmo(pBot) == 0) + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) == 0) { if (BotGetSecondaryWeaponClipAmmo(pBot) > 0) { @@ -519,12 +579,12 @@ AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target) } else { - return UTIL_GetBotPrimaryWeapon(pBot); + return UTIL_GetPlayerPrimaryWeapon(pBot->Player); } } else { - AvHAIWeapon PrimaryWeapon = UTIL_GetBotPrimaryWeapon(pBot); + AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); if (PrimaryWeapon == WEAPON_MARINE_SHOTGUN) { @@ -536,7 +596,7 @@ AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target) } else { - if (BotGetPrimaryWeaponClipAmmo(pBot) > 0 || BotGetPrimaryWeaponAmmoReserve(pBot) > 0) + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) { return PrimaryWeapon; } @@ -548,7 +608,7 @@ AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target) } else { - if (BotGetPrimaryWeaponClipAmmo(pBot) > 0 || BotGetPrimaryWeaponAmmoReserve(pBot) > 0) + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) { return PrimaryWeapon; } @@ -569,7 +629,7 @@ AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target) { if (DistFromEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f))) { - if (BotGetPrimaryWeaponClipAmmo(pBot) > 0 || BotGetPrimaryWeaponAmmoReserve(pBot) > 0) + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) { return PrimaryWeapon; } @@ -583,7 +643,7 @@ AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target) } else { - if (BotGetPrimaryWeaponClipAmmo(pBot) > 0 || (DistFromEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f)) && BotGetPrimaryWeaponAmmoReserve(pBot) > 0)) + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || (DistFromEnemy > sqrf(UTIL_MetresToGoldSrcUnits(5.0f)) && UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0)) { return PrimaryWeapon; } @@ -614,7 +674,7 @@ AvHAIWeapon BotAlienChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* tar if (StructureType == STRUCTURE_NONE) { - return UTIL_GetBotPrimaryWeapon(pBot); + return UTIL_GetPlayerPrimaryWeapon(pBot->Player); } if (PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB)) @@ -651,7 +711,7 @@ AvHAIWeapon BotAlienChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* tar } } - return UTIL_GetBotPrimaryWeapon(pBot); + return UTIL_GetPlayerPrimaryWeapon(pBot->Player); } AvHAIWeapon BotMarineChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* target) @@ -660,9 +720,9 @@ AvHAIWeapon BotMarineChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* ta if (StructureType == STRUCTURE_NONE) { - if (BotGetPrimaryWeaponClipAmmo(pBot) > 0 || BotGetPrimaryWeaponAmmoReserve(pBot) > 0) + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) { - return UTIL_GetBotPrimaryWeapon(pBot); + return UTIL_GetPlayerPrimaryWeapon(pBot->Player); } else if (BotGetSecondaryWeaponClipAmmo(pBot) > 0 || BotGetSecondaryWeaponAmmoReserve(pBot) > 0) { @@ -676,9 +736,9 @@ AvHAIWeapon BotMarineChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* ta if (StructureType == STRUCTURE_ALIEN_HIVE || StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER) { - if (BotGetPrimaryWeaponClipAmmo(pBot) > 0 || BotGetPrimaryWeaponAmmoReserve(pBot) > 0) + if (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0) { - return UTIL_GetBotPrimaryWeapon(pBot); + return UTIL_GetPlayerPrimaryWeapon(pBot->Player); } else if (BotGetSecondaryWeaponClipAmmo(pBot) > 0 || BotGetSecondaryWeaponAmmoReserve(pBot) > 0) { @@ -691,9 +751,9 @@ AvHAIWeapon BotMarineChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* ta } else { - AvHAIWeapon PrimaryWeapon = UTIL_GetBotPrimaryWeapon(pBot); + AvHAIWeapon PrimaryWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player); - if ((PrimaryWeapon == WEAPON_MARINE_GL || PrimaryWeapon == WEAPON_MARINE_SHOTGUN) && (BotGetPrimaryWeaponClipAmmo(pBot) > 0 || BotGetPrimaryWeaponAmmoReserve(pBot) > 0)) + if ((PrimaryWeapon == WEAPON_MARINE_GL || PrimaryWeapon == WEAPON_MARINE_SHOTGUN) && (UTIL_GetPlayerPrimaryWeaponClipAmmo(pBot->Player) > 0 || UTIL_GetPlayerPrimaryAmmoReserve(pBot->Player) > 0)) { return PrimaryWeapon; } @@ -701,7 +761,7 @@ AvHAIWeapon BotMarineChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* ta return WEAPON_MARINE_KNIFE; } - return UTIL_GetBotPrimaryWeapon(pBot); + return UTIL_GetPlayerPrimaryWeapon(pBot->Player); } AvHAIWeapon GorgeGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target) @@ -875,7 +935,7 @@ AvHAIWeapon FadeGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target) void BotReloadCurrentWeapon(AvHAIPlayer* pBot) { - AvHAIWeapon CurrentWeapon = GetBotCurrentWeapon(pBot); + AvHAIWeapon CurrentWeapon = GetPlayerCurrentWeapon(pBot->Player); if (!WeaponCanBeReloaded(CurrentWeapon)) { return; } diff --git a/main/source/mod/AIPlayers/AvHAIWeaponHelper.h b/main/source/mod/AIPlayers/AvHAIWeaponHelper.h index 157030c9..0e61fb39 100644 --- a/main/source/mod/AIPlayers/AvHAIWeaponHelper.h +++ b/main/source/mod/AIPlayers/AvHAIWeaponHelper.h @@ -5,19 +5,25 @@ #include "AvHAIPlayer.h" -int BotGetCurrentWeaponClipAmmo(const AvHAIPlayer* pBot); -int BotGetCurrentWeaponMaxClipAmmo(const AvHAIPlayer* pBot); -int BotGetCurrentWeaponReserveAmmo(const AvHAIPlayer* pBot); -AvHAIWeapon GetBotCurrentWeapon(const AvHAIPlayer* pBot); +int GetPlayerCurrentWeaponClipAmmo(const AvHPlayer* Player); +int GetPlayerCurrentWeaponMaxClipAmmo(const AvHPlayer* Player); +int GetPlayerCurrentWeaponReserveAmmo(const AvHPlayer* Player); +AvHAIWeapon GetPlayerCurrentWeapon(const AvHPlayer* Player); AvHBasePlayerWeapon* GetPlayerCurrentWeaponReference(const AvHPlayer* Player); -AvHAIWeapon UTIL_GetBotPrimaryWeapon(const AvHAIPlayer* pBot); +AvHAIWeapon UTIL_GetPlayerPrimaryWeapon(const AvHPlayer* Player); +AvHAIWeapon UTIL_GetPlayerSecondaryWeapon(const AvHPlayer* Player); -int BotGetPrimaryWeaponClipAmmo(const AvHAIPlayer* pBot); -int BotGetPrimaryWeaponMaxClipSize(const AvHAIPlayer* pBot); -int BotGetPrimaryWeaponAmmoReserve(AvHAIPlayer* pBot); -int BotGetPrimaryWeaponMaxAmmoReserve(AvHAIPlayer* pBot); +int UTIL_GetPlayerPrimaryWeaponClipAmmo(const AvHPlayer* Player); +int UTIL_GetPlayerPrimaryWeaponMaxClipSize(const AvHPlayer* Player); +int UTIL_GetPlayerPrimaryAmmoReserve(AvHPlayer* Player); +int UTIL_GetPlayerPrimaryMaxAmmoReserve(AvHPlayer* Player); + +int UTIL_GetPlayerSecondaryWeaponClipAmmo(const AvHPlayer* Player); +int UTIL_GetPlayerSecondaryWeaponMaxClipSize(const AvHPlayer* Player); +int UTIL_GetPlayerSecondaryAmmoReserve(AvHPlayer* Player); +int UTIL_GetPlayerSecondaryMaxAmmoReserve(AvHPlayer* Player); AvHAIWeapon GetBotMarineSecondaryWeapon(const AvHAIPlayer* pBot); int BotGetSecondaryWeaponClipAmmo(const AvHAIPlayer* pBot); diff --git a/main/source/mod/AvHConsoleCommands.cpp b/main/source/mod/AvHConsoleCommands.cpp index 670bedcd..bf77d7ee 100644 --- a/main/source/mod/AvHConsoleCommands.cpp +++ b/main/source/mod/AvHConsoleCommands.cpp @@ -113,6 +113,7 @@ #include "AIPlayers/AvHAIPlayerManager.h" #include "AIPlayers/AvHAITask.h" #include "AIPlayers/AvHAITactical.h" +#include "AIPlayers/AvHAICommander.h" extern AvHParticleTemplateListServer gParticleTemplateList; extern CVoiceGameMgr g_VoiceGameMgr; @@ -1497,9 +1498,58 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) theSuccess = true; } - else if (FStrEq(pcmd, "testpathfind")) + else if (FStrEq(pcmd, "testcommanderbuild")) { - AIDEBUG_TestPathFind(); + AvHAIPlayer* AIComm = AIMGR_GetAICommander(theAvHPlayer->GetTeam()); + + if (AIComm) + { + + Vector TraceStart = GetPlayerEyePosition(theAvHPlayer->edict()); // origin + pev->view_ofs + Vector LookDir = UTIL_GetForwardVector(theAvHPlayer->edict()->v.v_angle); // Converts view angles to normalized unit vector + + Vector TraceEnd = TraceStart + (LookDir * 1000.0f); + + TraceResult Hit; + + UTIL_TraceLine(TraceStart, TraceEnd, ignore_monsters, theAvHPlayer->edict(), &Hit); + + if (Hit.flFraction < 1.0f) + { + AICOMM_DeployStructure(AIComm, STRUCTURE_MARINE_ARMOURY, Hit.vecEndPos); + } + + } + + theSuccess = true; + } + else if (FStrEq(pcmd, "testresearchavailable")) + { + AvHTeam* PlayerTeam = GetGameRules()->GetTeam(theAvHPlayer->GetTeam()); + + if (PlayerTeam) + { + AvHMessageID Message = RESEARCH_ARMOR_ONE; + AvHMessageID Message2 = RESEARCH_ARMOR_TWO; + + if (PlayerTeam->GetResearchManager().GetIsMessageAvailable(Message)) + { + UTIL_SayText("Armour 1: TRUE\n", theAvHPlayer); + } + else + { + UTIL_SayText("Armour 1: FALSE\n", theAvHPlayer); + } + + if (PlayerTeam->GetResearchManager().GetIsMessageAvailable(Message2)) + { + UTIL_SayText("Armour 2: TRUE\n", theAvHPlayer); + } + else + { + UTIL_SayText("Armour 2: FALSE\n", theAvHPlayer); + } + } theSuccess = true; } diff --git a/main/source/mod/AvHMarineEquipment.cpp b/main/source/mod/AvHMarineEquipment.cpp index 43b6dede..6cc8f1e7 100644 --- a/main/source/mod/AvHMarineEquipment.cpp +++ b/main/source/mod/AvHMarineEquipment.cpp @@ -1941,7 +1941,7 @@ void AvHCommandStation::CommandUse( CBaseEntity* pActivator, CBaseEntity* pCalle AvHPlayer* thePlayer = dynamic_cast(pActivator); // Mapper-placed CCs can be killed but they don't go away - if(thePlayer && !(thePlayer->pev->flags & FL_FAKECLIENT) && !this->GetHasBeenKilled() && thePlayer->GetIsAbleToAct()) + if(thePlayer && !this->GetHasBeenKilled() && thePlayer->GetIsAbleToAct()) { AvHTeam* theTeam = thePlayer->GetTeamPointer(); if(theTeam && (theTeam->GetTeamType() == AVH_CLASS_TYPE_MARINE)) diff --git a/main/source/mod/AvHPlayer.h b/main/source/mod/AvHPlayer.h index 1d73f302..644fbd42 100644 --- a/main/source/mod/AvHPlayer.h +++ b/main/source/mod/AvHPlayer.h @@ -486,6 +486,8 @@ public: // bool GetHasSeenATeam(); + + void GiveOrderToSelection(AvHOrder& inOrder); private: void AcquireOverwatchTarget(); bool AttemptToBuildAlienStructure(AvHMessageID inMessageID); @@ -509,7 +511,7 @@ private: void GetSpeeds(int& outBaseSpeed, int& outUnemcumberedSpeed) const; void GiveCombatUpgradesOnSpawn(); bool GiveOrderToSelection(AvHOrderType inOrder, Vector inNormRay); - void GiveOrderToSelection(AvHOrder& inOrder); + void GiveUpgrade(AvHMessageID inUpgrade); void HandleOverwatch(void); void HandleResearch(); diff --git a/main/source/mod/AvHResearchManager.cpp b/main/source/mod/AvHResearchManager.cpp index b8833bff..36ef8601 100644 --- a/main/source/mod/AvHResearchManager.cpp +++ b/main/source/mod/AvHResearchManager.cpp @@ -196,6 +196,11 @@ void AvHResearchManager::SetTeamNumber(AvHTeamNumber inTeamNumber) this->mTeamNumber = inTeamNumber; } +bool AvHResearchManager::GetResearchDone(AvHTechID inTech) +{ + return this->mTechNodes.GetIsTechResearched(inTech); +} + bool AvHResearchManager::SetResearchDone(AvHMessageID inTech, int inEntityIndex) { bool theFoundIt = false; diff --git a/main/source/mod/AvHResearchManager.h b/main/source/mod/AvHResearchManager.h index f0e26fbb..c46b5f6c 100644 --- a/main/source/mod/AvHResearchManager.h +++ b/main/source/mod/AvHResearchManager.h @@ -79,6 +79,8 @@ public: void SetTeamNumber(AvHTeamNumber inTeamNumber); void UpdateResearch(); + + bool GetResearchDone(AvHTechID inTech); private: bool SetResearchDone(AvHMessageID inTech, int inEntityIndex); diff --git a/main/source/mod/AvHSharedUtil.h b/main/source/mod/AvHSharedUtil.h index 9cfba11f..50c58ec3 100644 --- a/main/source/mod/AvHSharedUtil.h +++ b/main/source/mod/AvHSharedUtil.h @@ -96,6 +96,7 @@ vec3_t AvHSHUGetRealLocation(const vec3_t& inLocation, const vec3_t& inMinBox, c void AvHSHUGetFirstNonSolidPoint(float* inStartPos, float* inEndPos, float* outNonSolidPoint); void AvHSHUGetFirstNonSolidPoint(const vec3_t& inStartPos, const vec3_t& inEndPos, vec3_t& outNonSolidPoint); bool AvHSHUGetIsBuilding(AvHMessageID inMessageID); +bool AvHSHUGetIsMarineStructure(AvHUser3 inUser3); bool AvHSHUGetIsBuildTech(AvHMessageID inMessageID); bool AvHSHUGetIsWeaponFocusable(AvHWeaponID inWeaponID); bool AvHSHUGetDoesTechCostEnergy(AvHMessageID inMessageID);