mirror of
https://github.com/ENSL/NS.git
synced 2025-04-20 16:30:56 +00:00
Continuing alien building and other non-combat logic
This commit is contained in:
parent
d3e1d18e11
commit
9e4c531346
12 changed files with 1371 additions and 403 deletions
|
@ -220,11 +220,13 @@ typedef struct _STRUCTURE_OBSTACLE
|
|||
typedef struct _RESOURCE_NODE
|
||||
{
|
||||
AvHFuncResource* ResourceEntity = nullptr; // The func_resource edict reference
|
||||
edict_t* ResourceEdict = nullptr;
|
||||
Vector Location = g_vecZero; // origin of the func_resource edict (not the tower itself)
|
||||
bool bIsOccupied = false; // True if there is any resource tower on it
|
||||
AvHTeamNumber OwningTeam = TEAM_IND; // The team that has currently capped this node (TEAM_IND if none)
|
||||
edict_t* ActiveTowerEntity = nullptr; // Reference to the resource tower edict (if capped)
|
||||
bool bIsBaseNode = false; // Is this a node in the marine base or active alien hive?
|
||||
edict_t* ParentHive = nullptr;
|
||||
unsigned int TeamAReachabilityFlags = AI_REACHABILITY_NONE; // Is this reachable by the bots? Checks for marine reachability only
|
||||
unsigned int TeamBReachabilityFlags = AI_REACHABILITY_NONE; // Is this reachable by the bots? Checks for marine reachability only
|
||||
bool bReachabilityMarkedDirty = false; // Reachability needs to be recalculated
|
||||
|
|
|
@ -449,8 +449,6 @@ void UTIL_DrawLine(edict_t* pEntity, Vector start, Vector end, int r, int g, int
|
|||
|
||||
void UTIL_DrawHUDText(edict_t* pEntity, char channel, float x, float y, unsigned char r, unsigned char g, unsigned char b, const char* string)
|
||||
{
|
||||
|
||||
|
||||
// higher level wrapper for hudtextparms TE_TEXTMESSAGEs. This function is meant to be called
|
||||
// every frame, since the duration of the display is roughly worth the duration of a video
|
||||
// frame. The X and Y coordinates are unary fractions which are bound to this rule:
|
||||
|
@ -483,7 +481,7 @@ void UTIL_DrawHUDText(edict_t* pEntity, char channel, float x, float y, unsigned
|
|||
WRITE_BYTE(1); // effect ALPHA
|
||||
WRITE_SHORT(0); // fade-in time in seconds * 256
|
||||
WRITE_SHORT(0); // fade-out time in seconds * 256
|
||||
WRITE_SHORT(1); // hold time in seconds * 256
|
||||
WRITE_SHORT(5); // hold time in seconds * 256
|
||||
WRITE_STRING(string);//string); // send the string
|
||||
MESSAGE_END(); // end
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "AvHAITask.h"
|
||||
#include "AvHAICommander.h"
|
||||
#include "AvHAIPlayerManager.h"
|
||||
#include "AvHAIConfig.h"
|
||||
|
||||
#include "../AvHGamerules.h"
|
||||
#include "../AvHMessage.h"
|
||||
|
@ -1612,15 +1613,8 @@ void StartNewBotFrame(AvHAIPlayer* pBot)
|
|||
|
||||
void CustomThink(AvHAIPlayer* pBot)
|
||||
{
|
||||
if (IsPlayerAlien(pBot->Edict))
|
||||
{
|
||||
if (!vIsZero(AIDEBUG_GetDebugVector1()))
|
||||
{
|
||||
const AvHAIHiveDefinition* Hive = AITAC_GetHiveNearestLocation(AIDEBUG_GetDebugVector1());
|
||||
UpdateAIAlienPlayerNSRole(pBot);
|
||||
|
||||
BotAlienBuildHive(pBot, Hive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DroneThink(AvHAIPlayer* pBot)
|
||||
|
@ -1730,20 +1724,25 @@ void UpdateAIAlienPlayerNSRole(AvHAIPlayer* pBot)
|
|||
return;
|
||||
}
|
||||
|
||||
// Don't switch roles if already fade/onos or those resources are potentially wasted
|
||||
if (IsPlayerFade(pBot->Edict) || IsPlayerOnos(pBot->Edict))
|
||||
if (AITAC_IsAlienCapperNeeded(pBot))
|
||||
{
|
||||
SetNewAIPlayerRole(pBot, BOT_ROLE_ASSAULT);
|
||||
SetNewAIPlayerRole(pBot, BOT_ROLE_FIND_RESOURCES);
|
||||
return;
|
||||
}
|
||||
|
||||
// Likewise for lerks
|
||||
if (IsPlayerLerk(pBot->Edict))
|
||||
if (AITAC_IsAlienBuilderNeeded(pBot))
|
||||
{
|
||||
SetNewAIPlayerRole(pBot, BOT_ROLE_BUILDER);
|
||||
return;
|
||||
}
|
||||
|
||||
if (AITAC_IsAlienHarasserNeeded(pBot))
|
||||
{
|
||||
SetNewAIPlayerRole(pBot, BOT_ROLE_HARASS);
|
||||
return;
|
||||
}
|
||||
|
||||
SetNewAIPlayerRole(pBot, BOT_ROLE_ASSAULT);
|
||||
|
||||
}
|
||||
|
||||
|
@ -1782,7 +1781,7 @@ void UpdateAIMarinePlayerNSRole(AvHAIPlayer* pBot)
|
|||
}
|
||||
|
||||
// If we own less than half the res nodes in the map, then we want 2 marines to cap them. Otherwise, have 1
|
||||
float ResNodeOwnership = AITAC_GetTeamResNodeOwnership(BotTeamNumber);
|
||||
float ResNodeOwnership = AITAC_GetTeamResNodeOwnership(BotTeamNumber, true);
|
||||
|
||||
int DesiredResCappers = (ResNodeOwnership < 0.5f) ? 2 : 1;
|
||||
|
||||
|
@ -2172,6 +2171,49 @@ void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
|||
void AIPlayerNSAlienThink(AvHAIPlayer* pBot)
|
||||
{
|
||||
UpdateAIAlienPlayerNSRole(pBot);
|
||||
|
||||
AvHAIHiveDefinition* HiveToBuild = nullptr;
|
||||
|
||||
if (AITAC_ShouldBotBuildHive(pBot, &HiveToBuild))
|
||||
{
|
||||
BotAlienBuildHive(pBot, HiveToBuild);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pBot->BotRole == BOT_ROLE_ASSAULT)
|
||||
{
|
||||
BotEvolveLifeform(pBot, pBot->Edict->v.origin, ALIEN_LIFEFORM_ONE);
|
||||
}
|
||||
|
||||
if (!pBot->CurrentTask) { pBot->CurrentTask = &pBot->PrimaryBotTask; }
|
||||
|
||||
if (gpGlobals->time < pBot->BotNextTaskEvaluationTime)
|
||||
{
|
||||
if (pBot->CurrentTask && pBot->CurrentTask->TaskType != TASK_NONE)
|
||||
{
|
||||
BotProgressTask(pBot, pBot->CurrentTask);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pBot->BotNextTaskEvaluationTime = gpGlobals->time + frandrange(0.2f, 0.5f);
|
||||
|
||||
AITASK_BotUpdateAndClearTasks(pBot);
|
||||
|
||||
AIPlayerSetPrimaryAlienTask(pBot, &pBot->PrimaryBotTask);
|
||||
AIPlayerSetSecondaryAlienTask(pBot, &pBot->SecondaryBotTask);
|
||||
|
||||
pBot->CurrentTask = AIPlayerGetNextTask(pBot);
|
||||
|
||||
if (pBot->CurrentTask && pBot->CurrentTask->TaskType != TASK_NONE)
|
||||
{
|
||||
BotProgressTask(pBot, pBot->CurrentTask);
|
||||
}
|
||||
|
||||
if (pBot->DesiredCombatWeapon == WEAPON_NONE)
|
||||
{
|
||||
pBot->DesiredCombatWeapon = UTIL_GetPlayerPrimaryWeapon(pBot->Player);
|
||||
}
|
||||
}
|
||||
|
||||
void AIPlayerCOThink(AvHAIPlayer* pBot)
|
||||
|
@ -2255,7 +2297,7 @@ void BotSwitchToWeapon(AvHAIPlayer* pBot, AvHAIWeapon NewWeaponSlot)
|
|||
|
||||
bool ShouldBotThink(AvHAIPlayer* pBot)
|
||||
{
|
||||
return (IsPlayerActiveInGame(pBot->Edict) || IsPlayerCommander(pBot->Edict)) && !IsPlayerGestating(pBot->Edict);
|
||||
return GetGameRules()->GetGameStarted() && (IsPlayerActiveInGame(pBot->Edict) || IsPlayerCommander(pBot->Edict)) && !IsPlayerGestating(pBot->Edict);
|
||||
}
|
||||
|
||||
void BotResumePlay(AvHAIPlayer* pBot)
|
||||
|
@ -2345,3 +2387,402 @@ void BotStopCommanderMode(AvHAIPlayer* pBot)
|
|||
pBot->Player->EffectivePlayerClassChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void AIPlayerSetPrimaryAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
||||
{
|
||||
switch (pBot->BotRole)
|
||||
{
|
||||
case BOT_ROLE_BUILDER:
|
||||
AIPlayerSetAlienBuilderPrimaryTask(pBot, Task);
|
||||
return;
|
||||
case BOT_ROLE_FIND_RESOURCES:
|
||||
AIPlayerSetAlienCapperPrimaryTask(pBot, Task);
|
||||
return;
|
||||
case BOT_ROLE_ASSAULT:
|
||||
AIPlayerSetAlienAssaultPrimaryTask(pBot, Task);
|
||||
return;
|
||||
case BOT_ROLE_HARASS:
|
||||
AIPlayerSetAlienHarasserPrimaryTask(pBot, Task);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void AIPlayerSetAlienBuilderPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
||||
{
|
||||
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
|
||||
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
|
||||
|
||||
// Do we have any missing upgrade chambers (should have 3 of each if we can build them)
|
||||
AvHAIDeployableStructureType MissingStructure = AITAC_GetNextMissingUpgradeChamberForTeam(BotTeam);
|
||||
|
||||
// If we do have a missing upgrade chamber, built it at the nearest hive or resource node that we own, whichever is nearest
|
||||
if (MissingStructure != STRUCTURE_NONE)
|
||||
{
|
||||
if (Task->TaskType == TASK_BUILD && Task->StructureType == MissingStructure) { return; }
|
||||
|
||||
vector<AvHAIHiveDefinition*> AllHives = AITAC_GetAllHives();
|
||||
|
||||
DeployableSearchFilter ResNodeFilter;
|
||||
ResNodeFilter.DeployableTeam = BotTeam;
|
||||
ResNodeFilter.ReachabilityTeam = BotTeam;
|
||||
ResNodeFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
|
||||
|
||||
AvHAIResourceNode* NearestNode = AITAC_FindNearestResourceNodeToLocation(pBot->Edict->v.origin, &ResNodeFilter);
|
||||
|
||||
Vector BuildOrigin = ZERO_VECTOR;
|
||||
|
||||
float MinDist = 0.0f;
|
||||
|
||||
for (auto it = AllHives.begin(); it != AllHives.end(); it++)
|
||||
{
|
||||
AvHAIHiveDefinition* ThisHive = (*it);
|
||||
|
||||
if (ThisHive->OwningTeam != BotTeam) { continue; }
|
||||
|
||||
float ThisDist = vDist2DSq(pBot->Edict->v.origin, ThisHive->FloorLocation);
|
||||
|
||||
if (vIsZero(BuildOrigin) || ThisDist < MinDist)
|
||||
{
|
||||
BuildOrigin = ThisHive->FloorLocation;
|
||||
MinDist = ThisDist;
|
||||
}
|
||||
}
|
||||
|
||||
if (NearestNode)
|
||||
{
|
||||
float ThisDist = vDist2DSq(pBot->Edict->v.origin, NearestNode->Location);
|
||||
|
||||
if (vIsZero(BuildOrigin) || ThisDist < MinDist)
|
||||
{
|
||||
BuildOrigin = NearestNode->Location;
|
||||
}
|
||||
}
|
||||
|
||||
if (vIsZero(BuildOrigin))
|
||||
{
|
||||
BuildOrigin = pBot->CurrentFloorPosition;
|
||||
}
|
||||
|
||||
if (Task->TaskType == TASK_BUILD && vDist2DSq(Task->TaskLocation, BuildOrigin) <= UTIL_MetresToGoldSrcUnits(5.0f))
|
||||
{
|
||||
Task->StructureType = MissingStructure;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector ActualBuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), BuildOrigin, UTIL_MetresToGoldSrcUnits(3.0f));
|
||||
|
||||
if (vIsZero(ActualBuildLocation))
|
||||
{
|
||||
ActualBuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(GORGE_BASE_NAV_PROFILE), BuildOrigin, UTIL_MetresToGoldSrcUnits(3.0f));
|
||||
}
|
||||
|
||||
AITASK_SetBuildTask(pBot, Task, MissingStructure, ActualBuildLocation, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// No missing upgrade chambers to drop, let's look for empty hives we can start staking a claim to, to deny to the enemy
|
||||
vector<AvHAIHiveDefinition*> AllHives = AITAC_GetAllHives();
|
||||
|
||||
AvHAIHiveDefinition* HiveToSecure = nullptr;
|
||||
|
||||
float MaxDist = 0.0f;
|
||||
|
||||
for (auto it = AllHives.begin(); it != AllHives.end(); it++)
|
||||
{
|
||||
AvHAIHiveDefinition* ThisHive = (*it);
|
||||
|
||||
if (ThisHive->Status == HIVE_STATUS_UNBUILT)
|
||||
{
|
||||
unsigned int StructureTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY);
|
||||
|
||||
if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_ALIEN)
|
||||
{
|
||||
StructureTypes = STRUCTURE_ALIEN_OFFENCECHAMBER;
|
||||
}
|
||||
|
||||
DeployableSearchFilter EnemyStructureFilter;
|
||||
EnemyStructureFilter.DeployableTeam = EnemyTeam;
|
||||
EnemyStructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
|
||||
EnemyStructureFilter.DeployableTypes = StructureTypes;
|
||||
EnemyStructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
|
||||
|
||||
bool bEnemyHaveFoothold = AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyStructureFilter);
|
||||
|
||||
if (bEnemyHaveFoothold) { continue; }
|
||||
|
||||
if (AITAC_GetNumPlayersOfTeamInArea(EnemyTeam, ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER) > 1) { continue; }
|
||||
|
||||
int OtherBuilders = AITAC_GetNumPlayersOfTeamAndClassInArea(EnemyTeam, ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_ALIEN_PLAYER2);
|
||||
|
||||
if (OtherBuilders >= 2) { continue; }
|
||||
|
||||
DeployableSearchFilter ExistingReinforcementFilter;
|
||||
ExistingReinforcementFilter.DeployableTeam = BotTeam;
|
||||
ExistingReinforcementFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
|
||||
ExistingReinforcementFilter.DeployableTypes = SEARCH_ALL_ALIEN_STRUCTURES;
|
||||
|
||||
vector<AvHAIBuildableStructure*> AllReinforcingStructures = AITAC_FindAllDeployables(ThisHive->FloorLocation, &ExistingReinforcementFilter);
|
||||
|
||||
int NumOCs = 0;
|
||||
int NumDCs = 0;
|
||||
int NumMCs = 0;
|
||||
int NumSCs = 0;
|
||||
|
||||
for (auto it = AllReinforcingStructures.begin(); it != AllReinforcingStructures.end(); it++)
|
||||
{
|
||||
switch ((*it)->StructureType)
|
||||
{
|
||||
case STRUCTURE_ALIEN_OFFENCECHAMBER:
|
||||
NumOCs++;
|
||||
break;
|
||||
case STRUCTURE_ALIEN_DEFENCECHAMBER:
|
||||
NumDCs++;
|
||||
break;
|
||||
case STRUCTURE_ALIEN_MOVEMENTCHAMBER:
|
||||
NumMCs++;
|
||||
break;
|
||||
case STRUCTURE_ALIEN_SENSORYCHAMBER:
|
||||
NumSCs++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (NumOCs < 3
|
||||
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_DEFENSE_CHAMBER) && NumDCs < 2)
|
||||
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_MOVEMENT_CHAMBER) && NumMCs < 1)
|
||||
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_SENSORY_CHAMBER) && NumSCs < 1))
|
||||
{
|
||||
float ThisDist = vDist2DSq(AITAC_GetTeamStartingLocation(EnemyTeam), ThisHive->FloorLocation);
|
||||
|
||||
if (ThisDist > MaxDist)
|
||||
{
|
||||
HiveToSecure = ThisHive;
|
||||
MaxDist = ThisDist;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (HiveToSecure)
|
||||
{
|
||||
AITASK_SetReinforceStructureTask(pBot, Task, HiveToSecure->HiveEntity->edict(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
DeployableSearchFilter ResNodeFilter;
|
||||
ResNodeFilter.DeployableTeam = BotTeam;
|
||||
ResNodeFilter.DeployableTypes = STRUCTURE_ALIEN_RESTOWER;
|
||||
ResNodeFilter.ReachabilityTeam = BotTeam;
|
||||
ResNodeFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
|
||||
|
||||
vector<AvHAIBuildableStructure*> AllMatchingTowers = AITAC_FindAllDeployables(pBot->Edict->v.origin, &ResNodeFilter);
|
||||
|
||||
edict_t* TowerToReinforce = nullptr;
|
||||
float MinDist = 0.0f;
|
||||
|
||||
for (auto it = AllMatchingTowers.begin(); it != AllMatchingTowers.end(); it++)
|
||||
{
|
||||
AvHAIBuildableStructure* ThisResTower = (*it);
|
||||
|
||||
DeployableSearchFilter ExistingReinforcementFilter;
|
||||
ExistingReinforcementFilter.DeployableTeam = BotTeam;
|
||||
ExistingReinforcementFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
|
||||
ExistingReinforcementFilter.DeployableTypes = SEARCH_ALL_ALIEN_STRUCTURES;
|
||||
|
||||
vector<AvHAIBuildableStructure*> AllReinforcingStructures = AITAC_FindAllDeployables(ThisResTower->Location, &ExistingReinforcementFilter);
|
||||
|
||||
int NumOCs = 0;
|
||||
int NumDCs = 0;
|
||||
int NumMCs = 0;
|
||||
int NumSCs = 0;
|
||||
|
||||
for (auto it = AllReinforcingStructures.begin(); it != AllReinforcingStructures.end(); it++)
|
||||
{
|
||||
switch ((*it)->StructureType)
|
||||
{
|
||||
case STRUCTURE_ALIEN_OFFENCECHAMBER:
|
||||
NumOCs++;
|
||||
break;
|
||||
case STRUCTURE_ALIEN_DEFENCECHAMBER:
|
||||
NumDCs++;
|
||||
break;
|
||||
case STRUCTURE_ALIEN_MOVEMENTCHAMBER:
|
||||
NumMCs++;
|
||||
break;
|
||||
case STRUCTURE_ALIEN_SENSORYCHAMBER:
|
||||
NumSCs++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (NumOCs < 3
|
||||
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_DEFENSE_CHAMBER) && NumDCs < 2)
|
||||
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_MOVEMENT_CHAMBER) && NumMCs < 1)
|
||||
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_SENSORY_CHAMBER) && NumSCs < 1))
|
||||
{
|
||||
float ThisDist = vDist2DSq(AITAC_GetTeamStartingLocation(EnemyTeam), ThisResTower->Location);
|
||||
|
||||
if (!TowerToReinforce || ThisDist < MinDist)
|
||||
{
|
||||
TowerToReinforce = ThisResTower->edict;
|
||||
MaxDist = ThisDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!FNullEnt(TowerToReinforce))
|
||||
{
|
||||
AITASK_SetReinforceStructureTask(pBot, Task, TowerToReinforce, false);
|
||||
}
|
||||
}
|
||||
|
||||
void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
||||
{
|
||||
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
|
||||
|
||||
AvHAIResourceNode* NodeToCap = nullptr;
|
||||
|
||||
float ResourcesRequired = BALANCE_VAR(kResourceTowerCost);
|
||||
|
||||
if (!IsPlayerGorge(pBot->Edict))
|
||||
{
|
||||
ResourcesRequired += BALANCE_VAR(kGorgeCost);
|
||||
}
|
||||
|
||||
bool bCanPlaceTower = pBot->Player->GetResources() >= (BALANCE_VAR(kResourceTowerCost) * 0.8f);
|
||||
|
||||
if (IsPlayerLerk(pBot->Edict) || IsPlayerFade(pBot->Edict) || IsPlayerOnos(pBot->Edict))
|
||||
{
|
||||
bCanPlaceTower = pBot->Player->GetResources() >= 75 && AITAC_GetTeamResNodeOwnership(BotTeam, true) >= 0.5f;
|
||||
}
|
||||
|
||||
bool bCanAttackTowers = (!IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB));
|
||||
|
||||
// If we have enough resources to cap a node, then find an empty one we can slap one down in
|
||||
if (bCanPlaceTower || !bCanAttackTowers)
|
||||
{
|
||||
DeployableSearchFilter EmptyNodeFilter;
|
||||
EmptyNodeFilter.DeployableTeam = TEAM_IND;
|
||||
EmptyNodeFilter.ReachabilityTeam = BotTeam;
|
||||
EmptyNodeFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
|
||||
|
||||
vector<AvHAIResourceNode*> EligibleNodes = AITAC_GetAllMatchingResourceNodes(pBot->Edict->v.origin, &EmptyNodeFilter);
|
||||
|
||||
float MinDist = 0.0f;
|
||||
|
||||
for (auto it = EligibleNodes.begin(); it != EligibleNodes.end(); it++)
|
||||
{
|
||||
AvHAIResourceNode* ThisNode = (*it);
|
||||
|
||||
edict_t* ExistingBuilder = AITAC_GetNearestPlayerOfClassInArea(BotTeam, ThisNode->Location, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2);
|
||||
|
||||
if (!FNullEnt(ExistingBuilder) && vDist2DSq(ExistingBuilder->v.origin, ThisNode->Location) < vDist2DSq(pBot->Edict->v.origin, ThisNode->Location) && GetPlayerResources(ExistingBuilder) >= (BALANCE_VAR(kResourceTowerCost) * 0.8f)) { continue; }
|
||||
|
||||
vector<AvHAIPlayer*> OtherAITeam = AIMGR_GetAIPlayersOnTeam(BotTeam);
|
||||
bool bNodeClaimed = false;
|
||||
|
||||
for (auto BotIt = OtherAITeam.begin(); BotIt != OtherAITeam.end(); BotIt++)
|
||||
{
|
||||
AvHAIPlayer* OtherBot = (*BotIt);
|
||||
|
||||
if (OtherBot != pBot && OtherBot->PrimaryBotTask.TaskType == TASK_CAP_RESNODE && OtherBot->PrimaryBotTask.TaskTarget == ThisNode->ResourceEdict)
|
||||
{
|
||||
bNodeClaimed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bNodeClaimed) { continue; }
|
||||
|
||||
float ThisDist = vDist2DSq(pBot->Edict->v.origin, ThisNode->Location);
|
||||
|
||||
if (!NodeToCap || ThisDist < MinDist)
|
||||
{
|
||||
NodeToCap = ThisNode;
|
||||
MinDist = ThisDist;
|
||||
}
|
||||
}
|
||||
|
||||
if (NodeToCap)
|
||||
{
|
||||
AITASK_SetCapResNodeTask(pBot, Task, NodeToCap, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Let's find an enemy tower to take out
|
||||
|
||||
DeployableSearchFilter EnemyNodeFilter;
|
||||
EnemyNodeFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam);
|
||||
EnemyNodeFilter.ReachabilityTeam = BotTeam;
|
||||
EnemyNodeFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
|
||||
|
||||
vector<AvHAIResourceNode*> EligibleNodes = AITAC_GetAllMatchingResourceNodes(pBot->Edict->v.origin, &EnemyNodeFilter);
|
||||
|
||||
float MinDist = 0.0f;
|
||||
|
||||
NodeToCap = nullptr;
|
||||
|
||||
for (auto it = EligibleNodes.begin(); it != EligibleNodes.end(); it++)
|
||||
{
|
||||
AvHAIResourceNode* ThisNode = (*it);
|
||||
|
||||
// Don't attack nodes which are firmly owned by the enemy (i.e. in marine base, or part of an enemy alien team's active hive)
|
||||
if (ThisNode->bIsBaseNode)
|
||||
{
|
||||
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
|
||||
if (AIMGR_GetEnemyTeamType(BotTeam) == AVH_CLASS_TYPE_MARINE)
|
||||
{
|
||||
// Too close to the marine comm chair, don't touch this one
|
||||
if (vDist2DSq(ThisNode->Location, AITAC_GetCommChairLocation(EnemyTeam)) < sqrf(UTIL_MetresToGoldSrcUnits(15.0f))) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// The enemy alien team has a hive here, don't attack this res node or we'll get smooshed
|
||||
AvHAIHiveDefinition* ParentHive = AITAC_GetHiveFromEdict(ThisNode->ParentHive);
|
||||
|
||||
if (ParentHive && ParentHive->OwningTeam == EnemyTeam) { continue; }
|
||||
}
|
||||
}
|
||||
|
||||
float ThisDist = vDist2DSq(ThisNode->Location, AITAC_GetTeamStartingLocation(BotTeam));
|
||||
|
||||
if (!NodeToCap || ThisDist < MinDist)
|
||||
{
|
||||
NodeToCap = ThisNode;
|
||||
MinDist = ThisDist;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (NodeToCap)
|
||||
{
|
||||
AITASK_SetAttackTask(pBot, Task, NodeToCap->ActiveTowerEntity, false);
|
||||
return;
|
||||
}
|
||||
|
||||
AIPlayerSetAlienAssaultPrimaryTask(pBot, Task);
|
||||
|
||||
}
|
||||
|
||||
void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AIPlayerSetAlienHarasserPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AIPlayerSetSecondaryAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
||||
{
|
||||
|
||||
}
|
|
@ -76,6 +76,14 @@ void AIPlayerSetMarineBombardierPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask*
|
|||
|
||||
void AIPlayerSetSecondaryMarineTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
|
||||
|
||||
void AIPlayerSetPrimaryAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
|
||||
void AIPlayerSetAlienBuilderPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
|
||||
void AIPlayerSetAlienCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
|
||||
void AIPlayerSetAlienAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
|
||||
void AIPlayerSetAlienHarasserPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
|
||||
|
||||
void AIPlayerSetSecondaryAlienTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task);
|
||||
|
||||
void BotSwitchToWeapon(AvHAIPlayer* pBot, AvHAIWeapon NewWeaponSlot);
|
||||
|
||||
bool ShouldBotThink(AvHAIPlayer* pBot);
|
||||
|
|
|
@ -593,7 +593,7 @@ void AIMGR_UpdateAIPlayers()
|
|||
|
||||
UpdateBotChat(bot);
|
||||
|
||||
CustomThink(bot);
|
||||
AIPlayerThink(bot);
|
||||
|
||||
BotUpdateDesiredViewRotation(bot);
|
||||
}
|
||||
|
@ -616,6 +616,52 @@ void AIMGR_UpdateAIPlayers()
|
|||
BotIt++;
|
||||
}
|
||||
|
||||
vector<AvHAIPlayer*> AlienPlayers = AIMGR_GetAIPlayersOnTeam(TEAM_TWO);
|
||||
|
||||
int NumBuilders = 0;
|
||||
int NumCappers = 0;
|
||||
int NumHarassers = 0;
|
||||
int NumAssault = 0;
|
||||
|
||||
for (auto it = AlienPlayers.begin(); it != AlienPlayers.end(); it++)
|
||||
{
|
||||
AvHAIPlayer* NewCapper = (*it);
|
||||
|
||||
if (NewCapper)
|
||||
{
|
||||
switch (NewCapper->BotRole)
|
||||
{
|
||||
case BOT_ROLE_BUILDER:
|
||||
NumBuilders++;
|
||||
break;
|
||||
case BOT_ROLE_FIND_RESOURCES:
|
||||
NumCappers++;
|
||||
break;
|
||||
case BOT_ROLE_HARASS:
|
||||
NumHarassers++;
|
||||
break;
|
||||
case BOT_ROLE_ASSAULT:
|
||||
NumAssault++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char buf[256];
|
||||
char interbuf[32];
|
||||
|
||||
sprintf(buf, "Builders: %d\n", NumBuilders);
|
||||
sprintf(interbuf, "Cappers: %d\n", NumCappers);
|
||||
strcat(buf, interbuf);
|
||||
sprintf(interbuf, "Harassers: %d\n", NumHarassers);
|
||||
strcat(buf, interbuf);
|
||||
sprintf(interbuf, "Assault: %d\n", NumAssault);
|
||||
strcat(buf, interbuf);
|
||||
|
||||
UTIL_DrawHUDText(INDEXENT(1), 0, 0.1f, 0.1f, 255, 255, 255, buf);
|
||||
|
||||
PrevTime = CurrTime;
|
||||
|
||||
}
|
||||
|
@ -708,6 +754,26 @@ int AIMGR_GetNumAIPlayersWithRoleOnTeam(AvHTeamNumber Team, AvHAIBotRole Role, A
|
|||
return Result;
|
||||
}
|
||||
|
||||
int AIMGR_GetNumHumansOfClassOnTeam(AvHTeamNumber Team, AvHUser3 PlayerType)
|
||||
{
|
||||
int Result = 0;
|
||||
|
||||
vector<AvHPlayer*> TeamPlayers = AIMGR_GetAllPlayersOnTeam(Team);
|
||||
|
||||
for (auto it = TeamPlayers.begin(); it != TeamPlayers.end(); it++)
|
||||
{
|
||||
AvHPlayer* ThisPlayer = (*it);
|
||||
edict_t* PlayerEdict = ThisPlayer->edict();
|
||||
|
||||
if (!(PlayerEdict->v.flags & FL_FAKECLIENT))
|
||||
{
|
||||
Result++;
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
int AIMGR_AIPlayerExistsOnTeam(AvHTeamNumber Team)
|
||||
{
|
||||
for (auto it = ActiveAIPlayers.begin(); it != ActiveAIPlayers.end(); it++)
|
||||
|
@ -847,6 +913,13 @@ AvHTeamNumber AIMGR_GetEnemyTeam(const AvHTeamNumber FriendlyTeam)
|
|||
return (FriendlyTeam == TeamANumber) ? TeamBNumber : TeamANumber;
|
||||
}
|
||||
|
||||
AvHClassType AIMGR_GetTeamType(const AvHTeamNumber Team)
|
||||
{
|
||||
AvHTeam* TeamRef = GetGameRules()->GetTeam(Team);
|
||||
|
||||
return (TeamRef) ? TeamRef->GetTeamType() : AVH_CLASS_TYPE_UNDEFINED;
|
||||
}
|
||||
|
||||
AvHClassType AIMGR_GetEnemyTeamType(const AvHTeamNumber FriendlyTeam)
|
||||
{
|
||||
AvHTeamNumber EnemyTeamNumber = AIMGR_GetEnemyTeam(FriendlyTeam);
|
||||
|
@ -891,6 +964,27 @@ vector<AvHAIPlayer*> AIMGR_GetAIPlayersOnTeam(AvHTeamNumber Team)
|
|||
return Result;
|
||||
}
|
||||
|
||||
vector<AvHPlayer*> AIMGR_GetNonAIPlayersOnTeam(AvHTeamNumber Team)
|
||||
{
|
||||
vector<AvHPlayer*> TeamPlayers = AIMGR_GetAllPlayersOnTeam(Team);
|
||||
|
||||
for (auto it = ActiveAIPlayers.begin(); it != ActiveAIPlayers.end(); it++)
|
||||
{
|
||||
AvHPlayer* ThisPlayer = it->Player;
|
||||
|
||||
if (!ThisPlayer) { continue; }
|
||||
|
||||
std::vector<AvHPlayer*>::iterator FoundPlayer = std::find(TeamPlayers.begin(), TeamPlayers.end(), ThisPlayer);
|
||||
|
||||
if (FoundPlayer != TeamPlayers.end())
|
||||
{
|
||||
TeamPlayers.erase(FoundPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
return TeamPlayers;
|
||||
}
|
||||
|
||||
void AIMGR_UpdateAIMapData()
|
||||
{
|
||||
AITAC_UpdateMapAIData();
|
||||
|
|
|
@ -60,16 +60,24 @@ int AIMGR_GetNumHumanPlayersOnTeam(AvHTeamNumber Team);
|
|||
|
||||
int AIMGR_GetNumAIPlayersWithRoleOnTeam(AvHTeamNumber Team, AvHAIBotRole Role, AvHAIPlayer* IgnoreAIPlayer);
|
||||
|
||||
int AIMGR_GetNumHumansOfClassOnTeam(AvHTeamNumber Team, AvHUser3 PlayerType);
|
||||
|
||||
AvHAIPlayer* AIMGR_GetAICommander(AvHTeamNumber Team);
|
||||
|
||||
|
||||
|
||||
AvHTeamNumber AIMGR_GetEnemyTeam(const AvHTeamNumber FriendlyTeam);
|
||||
AvHClassType AIMGR_GetEnemyTeamType(const AvHTeamNumber FriendlyTeam);
|
||||
AvHClassType AIMGR_GetTeamType(const AvHTeamNumber Team);
|
||||
|
||||
// Returns all NS AI players. Does not include third-party bots
|
||||
vector<AvHAIPlayer*> AIMGR_GetAllAIPlayers();
|
||||
// Returns all NS AI players on the requested team. Does not include third-party bots
|
||||
vector<AvHAIPlayer*> AIMGR_GetAIPlayersOnTeam(AvHTeamNumber Team);
|
||||
|
||||
// Returns all players on a team which are not an internal NS bot. Will still include third party bots such as Whichbot and RCBot
|
||||
vector<AvHPlayer*> AIMGR_GetNonAIPlayersOnTeam(AvHTeamNumber Team);
|
||||
|
||||
void AIMGR_ClearBotData();
|
||||
|
||||
AvHAIPlayer* AIMGR_GetDebugAIPlayer();
|
||||
|
|
|
@ -663,7 +663,16 @@ void AITAC_PopulateHiveData()
|
|||
AvHAIHiveDefinition NewHive;
|
||||
NewHive.HiveEntity = theEntity;
|
||||
NewHive.Location = theEntity->pev->origin;
|
||||
NewHive.HiveResNodeRef = AITAC_GetNearestResourceNodeToLocation(theEntity->pev->origin);
|
||||
|
||||
AvHAIResourceNode* NearestNode = AITAC_GetNearestResourceNodeToLocation(theEntity->pev->origin);
|
||||
|
||||
if (NearestNode)
|
||||
{
|
||||
NewHive.HiveResNodeRef = NearestNode;
|
||||
NearestNode->bIsBaseNode = true;
|
||||
NearestNode->ParentHive = NewHive.HiveEntity->edict();
|
||||
}
|
||||
|
||||
NewHive.FloorLocation = UTIL_GetFloorUnderEntity(theEntity->edict()); // Some hives are suspended in the air, this is the floor location directly beneath it
|
||||
|
||||
Hives.push_back(NewHive);
|
||||
|
@ -1039,6 +1048,7 @@ void AITAC_PopulateResourceNodes()
|
|||
|
||||
AvHAIResourceNode NewResNode;
|
||||
NewResNode.ResourceEntity = theEntity;
|
||||
NewResNode.ResourceEdict = theEntity->edict();
|
||||
NewResNode.Location = theEntity->pev->origin;
|
||||
NewResNode.TeamAReachabilityFlags = AI_REACHABILITY_NONE;
|
||||
NewResNode.TeamBReachabilityFlags = AI_REACHABILITY_NONE;
|
||||
|
@ -2291,7 +2301,7 @@ AvHAIResourceNode* AITAC_GetResourceNodeFromEdict(const edict_t* Edict)
|
|||
{
|
||||
for (auto it = ResourceNodes.begin(); it != ResourceNodes.end(); it++)
|
||||
{
|
||||
if (it->ResourceEntity->edict() == Edict)
|
||||
if (it->ResourceEdict == Edict)
|
||||
{
|
||||
return &(*it);
|
||||
}
|
||||
|
@ -2380,11 +2390,19 @@ AvHAIResourceNode* AITAC_GetNearestResourceNodeToLocation(const Vector Location)
|
|||
return Result;
|
||||
}
|
||||
|
||||
float AITAC_GetTeamResNodeOwnership(const AvHTeamNumber Team)
|
||||
float AITAC_GetTeamResNodeOwnership(const AvHTeamNumber Team, bool bIncludeBaseNodes)
|
||||
{
|
||||
int NumViableResNodes = 0;
|
||||
int NumOwnedResNodes = 0;
|
||||
|
||||
AvHAIResourceNode* MarineBaseNode = nullptr;
|
||||
bool bIsMarineType = AIMGR_GetTeamType(Team) == AVH_CLASS_TYPE_MARINE;
|
||||
|
||||
if (bIsMarineType)
|
||||
{
|
||||
MarineBaseNode = AITAC_GetNearestResourceNodeToLocation(AITAC_GetCommChairLocation(Team));
|
||||
}
|
||||
|
||||
for (auto it = ResourceNodes.begin(); it != ResourceNodes.end(); it++)
|
||||
{
|
||||
unsigned int CheckReachabilityFlags = (it->TeamAReachabilityFlags | it->TeamBReachabilityFlags);
|
||||
|
@ -2396,6 +2414,23 @@ float AITAC_GetTeamResNodeOwnership(const AvHTeamNumber Team)
|
|||
|
||||
if (CheckReachabilityFlags == AI_REACHABILITY_UNREACHABLE) { continue; }
|
||||
|
||||
if (!bIncludeBaseNodes)
|
||||
{
|
||||
if (it->bIsBaseNode)
|
||||
{
|
||||
if (bIsMarineType)
|
||||
{
|
||||
if (it->ResourceEntity == MarineBaseNode->ResourceEntity) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
const AvHAIHiveDefinition* NearestHive = AITAC_GetHiveNearestLocation(it->Location);
|
||||
|
||||
if (NearestHive->Status != HIVE_STATUS_UNBUILT) { continue; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NumViableResNodes++;
|
||||
|
||||
if (it->OwningTeam == Team)
|
||||
|
@ -2547,6 +2582,33 @@ int AITAC_GetNumActivePlayersOnTeam(const AvHTeamNumber Team)
|
|||
return Result;
|
||||
}
|
||||
|
||||
int AITAC_GetNumPlayersOfTeamAndClassInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 SearchClass)
|
||||
{
|
||||
int Result = 0;
|
||||
float MaxRadiusSq = sqrf(SearchRadius);
|
||||
|
||||
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
||||
{
|
||||
edict_t* PlayerEdict = INDEXENT(i);
|
||||
|
||||
if (FNullEnt(PlayerEdict) || PlayerEdict->free || PlayerEdict == IgnorePlayer) { continue; }
|
||||
|
||||
AvHPlayer* PlayerRef = dynamic_cast<AvHPlayer*>(CBaseEntity::Instance(PlayerEdict));
|
||||
|
||||
if (PlayerRef != nullptr && GetPlayerActiveClass(PlayerRef) == SearchClass && (Team == TEAM_IND || PlayerRef->GetTeam() == Team) && IsPlayerActiveInGame(PlayerEdict))
|
||||
{
|
||||
float Dist = (bConsiderPhaseDist) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(PlayerEdict->v.origin, SearchLocation)) : vDist2DSq(PlayerEdict->v.origin, SearchLocation);
|
||||
|
||||
if (Dist <= MaxRadiusSq)
|
||||
{
|
||||
Result++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
int AITAC_GetNumPlayersOfTeamInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 IgnoreClass)
|
||||
{
|
||||
int Result = 0;
|
||||
|
@ -2627,6 +2689,35 @@ edict_t* AITAC_GetNearestPlayerOfClassInArea(const AvHTeamNumber Team, const Vec
|
|||
|
||||
}
|
||||
|
||||
vector<edict_t*> AITAC_GetAllPlayersOfClassInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 SearchClass)
|
||||
{
|
||||
vector<edict_t*> Result;
|
||||
float MaxRadiusSq = sqrf(SearchRadius);
|
||||
float MinDistSq = 0.0f;
|
||||
|
||||
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
||||
{
|
||||
edict_t* PlayerEdict = INDEXENT(i);
|
||||
|
||||
if (FNullEnt(PlayerEdict) || PlayerEdict->free || PlayerEdict == IgnorePlayer) { continue; }
|
||||
|
||||
AvHPlayer* PlayerRef = dynamic_cast<AvHPlayer*>(CBaseEntity::Instance(PlayerEdict));
|
||||
|
||||
if (PlayerRef != nullptr && (SearchClass == AVH_USER3_NONE || GetPlayerActiveClass(PlayerRef) == SearchClass) && (Team == TEAM_IND || PlayerRef->GetTeam() == Team) && IsPlayerActiveInGame(PlayerEdict))
|
||||
{
|
||||
float Dist = (bConsiderPhaseDist) ? sqrf(AITAC_GetPhaseDistanceBetweenPoints(PlayerEdict->v.origin, SearchLocation)) : vDist2DSq(PlayerEdict->v.origin, SearchLocation);
|
||||
|
||||
if (Dist <= MaxRadiusSq)
|
||||
{
|
||||
Result.push_back(PlayerEdict);
|
||||
MinDistSq = Dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
AvHAIHiveDefinition* AITAC_GetTeamHiveWithTech(const AvHTeamNumber Team, const AvHMessageID Tech)
|
||||
{
|
||||
AvHTeam* TeamRef = GetGameRules()->GetTeam(Team);
|
||||
|
@ -3094,7 +3185,7 @@ AvHAIDeployableStructureType UTIL_GetChamberTypeForHiveTech(AvHMessageID HiveTec
|
|||
case ALIEN_BUILD_MOVEMENT_CHAMBER:
|
||||
return STRUCTURE_ALIEN_MOVEMENTCHAMBER;
|
||||
case ALIEN_BUILD_SENSORY_CHAMBER:
|
||||
return STRUCTURE_ALIEN_OFFENCECHAMBER;
|
||||
return STRUCTURE_ALIEN_SENSORYCHAMBER;
|
||||
default:
|
||||
return STRUCTURE_NONE;
|
||||
}
|
||||
|
@ -3302,9 +3393,196 @@ bool AITAC_AnyPlayerOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, fl
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AITAC_IsAlienBuilderNeeded(AvHAIPlayer* pBot)
|
||||
bool AITAC_IsAlienHarasserNeeded(AvHAIPlayer* pBot)
|
||||
{
|
||||
if (IsPlayerLerk(pBot->Edict)) { return true; }
|
||||
|
||||
if (pBot->Player->GetResources() < BALANCE_VAR(kLerkCost)) { return false; }
|
||||
|
||||
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
|
||||
|
||||
int NumTeamPlayers = AIMGR_GetNumPlayersOnTeam(BotTeam);
|
||||
int DesiredLerks = (int)ceilf((float)NumTeamPlayers * 0.1f);
|
||||
int NumLerks = imaxi(AITAC_GetNumPlayersOnTeamOfClass(BotTeam, AVH_USER3_ALIEN_PLAYER3, nullptr), AIMGR_GetNumAIPlayersWithRoleOnTeam(BotTeam, BOT_ROLE_HARASS, pBot));
|
||||
|
||||
return NumLerks < DesiredLerks;
|
||||
}
|
||||
|
||||
bool AITAC_ShouldBotBuildHive(AvHAIPlayer* pBot, AvHAIHiveDefinition** EligibleHive)
|
||||
{
|
||||
*EligibleHive = nullptr;
|
||||
|
||||
float HiveCost = BALANCE_VAR(kHiveCost);
|
||||
|
||||
if (!IsPlayerGorge(pBot->Edict))
|
||||
{
|
||||
HiveCost += BALANCE_VAR(kGorgeCost);
|
||||
}
|
||||
|
||||
if (pBot->Player->GetResources() < HiveCost) { return false; }
|
||||
|
||||
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
|
||||
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
|
||||
|
||||
// If we're a higher lifeform, ensure we can't leave this to someone else before considering losing those resources
|
||||
// We will ignore humans and third party bots, because we don't know if they will drop the hive or not. Not everyone can be as team-spirited as us...
|
||||
if (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict))
|
||||
{
|
||||
vector<AvHAIPlayer*> OtherAITeamMates = AIMGR_GetAIPlayersOnTeam(BotTeam);
|
||||
|
||||
for (auto it = OtherAITeamMates.begin(); it != OtherAITeamMates.end(); it++)
|
||||
{
|
||||
AvHAIPlayer* OtherBot = (*it);
|
||||
|
||||
// If the other bot has enough resources to drop a hive, and they're a less expensive life form than us, let them do it.
|
||||
if (OtherBot->Player->GetResources() >= BALANCE_VAR(kHiveCost) * 0.8f && OtherBot->Player->GetUser3() < pBot->Player->GetUser3()) { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
AvHAIHiveDefinition* SuitableHive = nullptr;
|
||||
float MinDist = 0.0f;
|
||||
|
||||
vector<AvHAIHiveDefinition*> AllHives = AITAC_GetAllHives();
|
||||
|
||||
for (auto it = AllHives.begin(); it != AllHives.end(); it++)
|
||||
{
|
||||
AvHAIHiveDefinition* ThisHive = (*it);
|
||||
|
||||
if (ThisHive->Status == HIVE_STATUS_BUILT) { continue; }
|
||||
|
||||
if (ThisHive->Status == HIVE_STATUS_BUILDING)
|
||||
{
|
||||
// Aliens can only build one hive at a time, so if we have one already under construction then automatic no
|
||||
if (ThisHive->OwningTeam == BotTeam)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// It's an enemy hive under construction, so we can't build this one but can keep searching
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check to make sure someone else isn't planning to drop a hive, otherwise don't bother
|
||||
bool bHasOtherBuilder = false;
|
||||
vector<edict_t*> OtherBuilders = AITAC_GetAllPlayersOfClassInArea(BotTeam, ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2);
|
||||
|
||||
for (auto BuildIt = OtherBuilders.begin(); BuildIt != OtherBuilders.end(); BuildIt++)
|
||||
{
|
||||
edict_t* OtherBuilder = (*BuildIt);
|
||||
|
||||
if (vDist2DSq(OtherBuilder->v.origin, ThisHive->FloorLocation) && GetPlayerResources(OtherBuilder) >= BALANCE_VAR(kHiveCost) * 0.8f) { bHasOtherBuilder = true; }
|
||||
}
|
||||
|
||||
if (bHasOtherBuilder) { return false; }
|
||||
|
||||
// Enemy are in here right now, wait until they're cleared out
|
||||
if (AITAC_GetNumPlayersOfTeamInArea(EnemyTeam, ThisHive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER) > 2) { continue; }
|
||||
|
||||
// Must be an empty hive
|
||||
DeployableSearchFilter EnemyFortificationsFilter;
|
||||
EnemyFortificationsFilter.DeployableTeam = EnemyTeam;
|
||||
EnemyFortificationsFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
|
||||
|
||||
if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_MARINE)
|
||||
{
|
||||
EnemyFortificationsFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY);
|
||||
EnemyFortificationsFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
|
||||
EnemyFortificationsFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; // This is important to prevent exploiting the AI. Those structures have to be built first!
|
||||
}
|
||||
else
|
||||
{
|
||||
EnemyFortificationsFilter.DeployableTypes = (STRUCTURE_ALIEN_OFFENCECHAMBER);
|
||||
}
|
||||
|
||||
// Enemy have built some stuff, wait until it's clear before building
|
||||
if (AITAC_DeployableExistsAtLocation(ThisHive->FloorLocation, &EnemyFortificationsFilter)) { continue; }
|
||||
|
||||
// Should be clear to drop dat hive!
|
||||
|
||||
float ThisDist = vDist2DSq(pBot->Edict->v.origin, ThisHive->FloorLocation);
|
||||
|
||||
if (!SuitableHive || ThisDist < MinDist)
|
||||
{
|
||||
SuitableHive = ThisHive;
|
||||
MinDist = ThisDist;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
*EligibleHive = SuitableHive;
|
||||
|
||||
return (SuitableHive != nullptr);
|
||||
}
|
||||
|
||||
bool AITAC_IsAlienCapperNeeded(AvHAIPlayer* pBot)
|
||||
{
|
||||
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
|
||||
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
|
||||
|
||||
float ResNodeOwnership = AITAC_GetTeamResNodeOwnership(BotTeam, false);
|
||||
float EnemyNodeOwnership = AITAC_GetTeamResNodeOwnership(EnemyTeam, false);
|
||||
|
||||
int NumTeamPlayers = AIMGR_GetNumPlayersOnTeam(BotTeam);
|
||||
int MaxCappers = (int)ceilf((float)NumTeamPlayers * 0.5f);
|
||||
|
||||
int NumCurrentCappers = AIMGR_GetNumAIPlayersWithRoleOnTeam(BotTeam, BOT_ROLE_FIND_RESOURCES, pBot);
|
||||
|
||||
int DesiredCappers = 0;
|
||||
|
||||
if (ResNodeOwnership > 0.6f) { return false; }
|
||||
|
||||
if (ResNodeOwnership < 0.25f)
|
||||
{
|
||||
DesiredCappers = MaxCappers;
|
||||
}
|
||||
else if (ResNodeOwnership < 0.4f)
|
||||
{
|
||||
DesiredCappers = imaxi(1, MaxCappers - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
DesiredCappers = imaxi(1, MaxCappers - 2);
|
||||
}
|
||||
|
||||
// If we're lerk/fade/onos, only go capper if we have lots of resources and can replace them easily, otherwise we can't afford to waste resources going back to gorge
|
||||
if (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict))
|
||||
{
|
||||
if (pBot->Player->GetResources() < 75 || ResNodeOwnership < 0.4f) { return false; }
|
||||
|
||||
int NumSkulks = AITAC_GetNumPlayersOnTeamOfClass(BotTeam, AVH_USER3_ALIEN_PLAYER1, nullptr);
|
||||
int NumGorges = AITAC_GetNumPlayersOnTeamOfClass(BotTeam, AVH_USER3_ALIEN_PLAYER2, nullptr);
|
||||
|
||||
// Don't go capping if there are other players who could do it instead and aren't higher life forms
|
||||
if ((NumSkulks + NumGorges) > DesiredCappers) { return false; }
|
||||
}
|
||||
|
||||
return NumCurrentCappers < DesiredCappers;
|
||||
}
|
||||
|
||||
bool AITAC_IsAlienBuilderNeeded(AvHAIPlayer* pBot)
|
||||
{
|
||||
// The basic logic here is that we have a max number of builders based on team size.
|
||||
// We add one extra required builder for every empty hive that needs fortifications placed
|
||||
// One for any missing upgrade chambers
|
||||
// and one if there are any resource towers that need reinforcing which aren't part of a hive
|
||||
// This should mean that we could have up to 3 builders at the start if the team is big enough, then that should reduce down to just 1 eventually
|
||||
|
||||
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
|
||||
AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam);
|
||||
|
||||
float ResNodeOwnership = AITAC_GetTeamResNodeOwnership(BotTeam, true);
|
||||
|
||||
// Don't lose all those resources!
|
||||
if (IsPlayerLerk(pBot->Edict) || IsPlayerFade(pBot->Edict) || IsPlayerOnos(pBot->Edict))
|
||||
{
|
||||
// Only waste those resources if we have loads of res nodes and can easily replenish the lost resources
|
||||
if (pBot->Player->GetResources() < 75 || ResNodeOwnership < 0.6f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AvHMessageID HiveTechOne = CONFIG_GetHiveTechAtIndex(0);
|
||||
AvHMessageID HiveTechTwo = CONFIG_GetHiveTechAtIndex(1);
|
||||
|
@ -3318,26 +3596,153 @@ bool AITAC_IsAlienBuilderNeeded(AvHAIPlayer* pBot)
|
|||
StructureFilter.DeployableTeam = BotTeam;
|
||||
|
||||
int NumTeamPlayers = AIMGR_GetNumPlayersOnTeam(BotTeam);
|
||||
int MaxBuilders = imini(2, (int)floorf((float)NumTeamPlayers * 0.5f));
|
||||
int NumCurrentBuilders = AIMGR_GetNumAIPlayersWithRoleOnTeam(BotTeam, BOT_ROLE_BUILDER, pBot);
|
||||
int MaxBuilders = (int)ceilf((float)NumTeamPlayers * 0.3f); // Max 1/3 of team can be builder at any one time
|
||||
|
||||
if (MaxBuilders == 0) { return false; }
|
||||
|
||||
// We have a hive without any associated chambers yet
|
||||
if (AITAC_TeamHiveWithTechExists(BotTeam, MESSAGE_NULL))
|
||||
// If we're struggling for resources, then cut back on max number of builders to have more focused on capping nodes
|
||||
if (ResNodeOwnership < 0.25f)
|
||||
{
|
||||
return NumCurrentBuilders < MaxBuilders;
|
||||
MaxBuilders = (imaxi(1, MaxBuilders - 1));
|
||||
}
|
||||
|
||||
StructureFilter.DeployableTypes = ChamberTypeOne;
|
||||
if (AITAC_TeamHiveWithTechExists(BotTeam, HiveTechOne) && AITAC_GetNumDeployablesNearLocation(pBot->Edict->v.origin, &StructureFilter) < 3) { return NumBuilders < 1; }
|
||||
|
||||
StructureFilter.DeployableTypes = ChamberTypeTwo;
|
||||
if (AITAC_TeamHiveWithTechExists(BotTeam, HiveTechTwo) && AITAC_GetNumDeployablesNearLocation(pBot->Edict->v.origin, &StructureFilter) < 3) { return NumBuilders < 1; }
|
||||
|
||||
StructureFilter.DeployableTypes = ChamberTypeThree;
|
||||
if (AITAC_TeamHiveWithTechExists(BotTeam, HiveTechThree) && AITAC_GetNumDeployablesNearLocation(pBot->Edict->v.origin, &StructureFilter) < 3) { return NumBuilders < 1; }
|
||||
// Don't build if we're a higher lifeform and there are others who could do the job instead
|
||||
if (!IsPlayerSkulk(pBot->Edict) && !IsPlayerGorge(pBot->Edict))
|
||||
{
|
||||
int NumSkulks = AITAC_GetNumPlayersOnTeamOfClass(BotTeam, AVH_USER3_ALIEN_PLAYER1, nullptr);
|
||||
int NumGorges = AITAC_GetNumPlayersOnTeamOfClass(BotTeam, AVH_USER3_ALIEN_PLAYER2, nullptr);
|
||||
int NumDead = AITAC_GetNumDeadPlayersOnTeam(BotTeam);
|
||||
|
||||
if ((NumSkulks + NumGorges + NumDead) > MaxBuilders) { return false; }
|
||||
}
|
||||
|
||||
|
||||
int NumCurrentBuilders = AIMGR_GetNumAIPlayersWithRoleOnTeam(BotTeam, BOT_ROLE_BUILDER, pBot);
|
||||
|
||||
vector<AvHPlayer*> NonAIPlayers = AIMGR_GetNonAIPlayersOnTeam(BotTeam);
|
||||
|
||||
for (auto it = NonAIPlayers.begin(); it != NonAIPlayers.end(); it++)
|
||||
{
|
||||
AvHPlayer* ThisPlayer = (*it);
|
||||
|
||||
if (GetPlayerActiveClass(ThisPlayer) == AVH_USER3_ALIEN_PLAYER2)
|
||||
{
|
||||
NumCurrentBuilders++;
|
||||
}
|
||||
}
|
||||
|
||||
// We already have too many gorges running around
|
||||
if (NumCurrentBuilders >= MaxBuilders) { return false; }
|
||||
|
||||
int DesiredBuilders = 0;
|
||||
|
||||
vector<AvHAIHiveDefinition*> AllHives = AITAC_GetAllHives();
|
||||
|
||||
for (auto it = AllHives.begin(); it != AllHives.end(); it++)
|
||||
{
|
||||
const AvHAIHiveDefinition* ThisHive = (*it);
|
||||
|
||||
// Can't build in an enemy hive (if playing AvA)
|
||||
if (ThisHive->OwningTeam == EnemyTeam) { continue; }
|
||||
|
||||
if (ThisHive->OwningTeam == BotTeam)
|
||||
{
|
||||
if (ThisHive->Status == HIVE_STATUS_BUILT)
|
||||
{
|
||||
// Hive hasn't got a chamber assigned yet, we need at least one builder for that
|
||||
if (ThisHive->TechStatus == MESSAGE_NULL)
|
||||
{
|
||||
DesiredBuilders++;
|
||||
|
||||
if (DesiredBuilders >= MaxBuilders)
|
||||
{
|
||||
return NumCurrentBuilders < DesiredBuilders;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
bool bEnemyIsMarines = (AIMGR_GetEnemyTeamType(BotTeam) == AVH_CLASS_TYPE_MARINE);
|
||||
|
||||
DeployableSearchFilter EnemyStructures;
|
||||
EnemyStructures.DeployableTeam = EnemyTeam;
|
||||
EnemyStructures.ReachabilityTeam = EnemyTeam;
|
||||
EnemyStructures.ReachabilityFlags = (bEnemyIsMarines) ? AI_REACHABILITY_MARINE : AI_REACHABILITY_SKULK;
|
||||
EnemyStructures.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
|
||||
|
||||
if (bEnemyIsMarines)
|
||||
{
|
||||
EnemyStructures.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
|
||||
EnemyStructures.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING;
|
||||
EnemyStructures.DeployableTypes = STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY;
|
||||
}
|
||||
else
|
||||
{
|
||||
EnemyStructures.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER;
|
||||
}
|
||||
|
||||
// Enemy have a foothold here, don't get involved
|
||||
if (AITAC_GetNumDeployablesNearLocation(ThisHive->Location, &EnemyStructures) > 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// The enemy don't have a proper foothold yet
|
||||
|
||||
DeployableSearchFilter ExistingReinforcementFilter;
|
||||
ExistingReinforcementFilter.DeployableTeam = BotTeam;
|
||||
ExistingReinforcementFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
|
||||
ExistingReinforcementFilter.DeployableTypes = SEARCH_ALL_ALIEN_STRUCTURES;
|
||||
|
||||
vector<AvHAIBuildableStructure*> AllReinforcingStructures = AITAC_FindAllDeployables(ThisHive->FloorLocation, &ExistingReinforcementFilter);
|
||||
|
||||
int NumOCs = 0;
|
||||
int NumDCs = 0;
|
||||
int NumMCs = 0;
|
||||
int NumSCs = 0;
|
||||
|
||||
for (auto it = AllReinforcingStructures.begin(); it != AllReinforcingStructures.end(); it++)
|
||||
{
|
||||
switch ((*it)->StructureType)
|
||||
{
|
||||
case STRUCTURE_ALIEN_OFFENCECHAMBER:
|
||||
NumOCs++;
|
||||
break;
|
||||
case STRUCTURE_ALIEN_DEFENCECHAMBER:
|
||||
NumDCs++;
|
||||
break;
|
||||
case STRUCTURE_ALIEN_MOVEMENTCHAMBER:
|
||||
NumMCs++;
|
||||
break;
|
||||
case STRUCTURE_ALIEN_SENSORYCHAMBER:
|
||||
NumSCs++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (NumOCs < 3
|
||||
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_DEFENSE_CHAMBER) && NumDCs < 2)
|
||||
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_MOVEMENT_CHAMBER) && NumMCs < 1)
|
||||
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_SENSORY_CHAMBER) && NumSCs < 1))
|
||||
{
|
||||
DesiredBuilders++;
|
||||
|
||||
if (DesiredBuilders >= MaxBuilders)
|
||||
{
|
||||
return NumCurrentBuilders < DesiredBuilders;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// We have hives to fortify and upgrades to enable. Ignore resource nodes for now. We will get a bot assigned to those once we've done everything else
|
||||
if (DesiredBuilders > 0)
|
||||
{
|
||||
return NumCurrentBuilders < DesiredBuilders;
|
||||
}
|
||||
|
||||
DeployableSearchFilter ResNodeFilter;
|
||||
ResNodeFilter.DeployableTeam = BotTeam;
|
||||
ResNodeFilter.ReachabilityTeam = BotTeam;
|
||||
|
@ -3349,7 +3754,121 @@ bool AITAC_IsAlienBuilderNeeded(AvHAIPlayer* pBot)
|
|||
{
|
||||
AvHAIResourceNode* ThisNode = (*it);
|
||||
|
||||
|
||||
if (ThisNode->bIsBaseNode && !FNullEnt(ThisNode->ParentHive))
|
||||
{
|
||||
AvHAIHiveDefinition* HiveRef = AITAC_GetHiveFromEdict(ThisNode->ParentHive);
|
||||
if (HiveRef && HiveRef->Status != HIVE_STATUS_UNBUILT)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
DeployableSearchFilter ExistingReinforcementFilter;
|
||||
ExistingReinforcementFilter.DeployableTeam = BotTeam;
|
||||
ExistingReinforcementFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
|
||||
ExistingReinforcementFilter.DeployableTypes = SEARCH_ALL_ALIEN_STRUCTURES;
|
||||
|
||||
vector<AvHAIBuildableStructure*> AllReinforcingStructures = AITAC_FindAllDeployables(ThisNode->Location, &ExistingReinforcementFilter);
|
||||
|
||||
int NumOCs = 0;
|
||||
int NumDCs = 0;
|
||||
int NumMCs = 0;
|
||||
int NumSCs = 0;
|
||||
|
||||
for (auto it = AllReinforcingStructures.begin(); it != AllReinforcingStructures.end(); it++)
|
||||
{
|
||||
switch ((*it)->StructureType)
|
||||
{
|
||||
case STRUCTURE_ALIEN_OFFENCECHAMBER:
|
||||
NumOCs++;
|
||||
break;
|
||||
case STRUCTURE_ALIEN_DEFENCECHAMBER:
|
||||
NumDCs++;
|
||||
break;
|
||||
case STRUCTURE_ALIEN_MOVEMENTCHAMBER:
|
||||
NumMCs++;
|
||||
break;
|
||||
case STRUCTURE_ALIEN_SENSORYCHAMBER:
|
||||
NumSCs++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (NumOCs < 2
|
||||
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_DEFENSE_CHAMBER) && NumDCs < 2)
|
||||
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_MOVEMENT_CHAMBER) && NumDCs < 1)
|
||||
|| (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_SENSORY_CHAMBER) && NumDCs < 1))
|
||||
{
|
||||
DesiredBuilders++;
|
||||
|
||||
if (DesiredBuilders >= MaxBuilders)
|
||||
{
|
||||
return NumCurrentBuilders < DesiredBuilders;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return NumCurrentBuilders < DesiredBuilders;
|
||||
|
||||
}
|
||||
|
||||
AvHAIDeployableStructureType AITAC_GetNextMissingUpgradeChamberForTeam(AvHTeamNumber Team)
|
||||
{
|
||||
if (AIMGR_GetTeamType(Team) != AVH_CLASS_TYPE_ALIEN) { return STRUCTURE_NONE; }
|
||||
|
||||
AvHMessageID HiveTechOne = CONFIG_GetHiveTechAtIndex(0);
|
||||
AvHMessageID HiveTechTwo = CONFIG_GetHiveTechAtIndex(1);
|
||||
AvHMessageID HiveTechThree = CONFIG_GetHiveTechAtIndex(2);
|
||||
|
||||
AvHAIDeployableStructureType ChamberTypeOne = UTIL_GetChamberTypeForHiveTech(HiveTechOne);
|
||||
AvHAIDeployableStructureType ChamberTypeTwo = UTIL_GetChamberTypeForHiveTech(HiveTechTwo);
|
||||
AvHAIDeployableStructureType ChamberTypeThree = UTIL_GetChamberTypeForHiveTech(HiveTechThree);
|
||||
|
||||
DeployableSearchFilter SearchFilter;
|
||||
SearchFilter.DeployableTeam = Team;
|
||||
|
||||
bool bHasFreeHive = AITAC_TeamHiveWithTechExists(Team, MESSAGE_NULL);
|
||||
|
||||
if (ChamberTypeOne != STRUCTURE_NONE && (bHasFreeHive || AITAC_TeamHiveWithTechExists(Team, HiveTechOne)))
|
||||
{
|
||||
SearchFilter.DeployableTypes = ChamberTypeOne;
|
||||
|
||||
int NumChambers = AITAC_GetNumDeployablesNearLocation(AITAC_GetTeamStartingLocation(Team), &SearchFilter);
|
||||
|
||||
if (NumChambers < 3)
|
||||
{
|
||||
return ChamberTypeOne;
|
||||
}
|
||||
}
|
||||
|
||||
if (ChamberTypeTwo != STRUCTURE_NONE && (bHasFreeHive || AITAC_TeamHiveWithTechExists(Team, HiveTechTwo)))
|
||||
{
|
||||
SearchFilter.DeployableTypes = ChamberTypeTwo;
|
||||
|
||||
int NumChambers = AITAC_GetNumDeployablesNearLocation(AITAC_GetTeamStartingLocation(Team), &SearchFilter);
|
||||
|
||||
if (NumChambers < 3)
|
||||
{
|
||||
return ChamberTypeTwo;
|
||||
}
|
||||
}
|
||||
|
||||
if (ChamberTypeThree != STRUCTURE_NONE && (bHasFreeHive || AITAC_TeamHiveWithTechExists(Team, HiveTechThree)))
|
||||
{
|
||||
SearchFilter.DeployableTypes = ChamberTypeThree;
|
||||
|
||||
int NumChambers = AITAC_GetNumDeployablesNearLocation(AITAC_GetTeamStartingLocation(Team), &SearchFilter);
|
||||
|
||||
if (NumChambers < 3)
|
||||
{
|
||||
return ChamberTypeThree;
|
||||
}
|
||||
}
|
||||
|
||||
return STRUCTURE_NONE;
|
||||
}
|
|
@ -107,7 +107,7 @@ AvHAIHiveDefinition* AITAC_GetHiveFromEdict(const edict_t* Edict);
|
|||
AvHAIResourceNode* AITAC_GetResourceNodeFromEdict(const edict_t* Edict);
|
||||
|
||||
// What percentage of all viable (can be reached by the requested team) resource nodes does the team currently own? Expressed as 0.0 - 1.0
|
||||
float AITAC_GetTeamResNodeOwnership(const AvHTeamNumber Team);
|
||||
float AITAC_GetTeamResNodeOwnership(const AvHTeamNumber Team, bool bIncludeBaseNodes);
|
||||
int AITAC_GetNumResourceNodesNearLocation(const Vector Location, const DeployableSearchFilter* Filter);
|
||||
AvHAIResourceNode* AITAC_FindNearestResourceNodeToLocation(const Vector Location, const DeployableSearchFilter* Filter);
|
||||
AvHAIResourceNode* AITAC_GetNearestResourceNodeToLocation(const Vector Location);
|
||||
|
@ -119,8 +119,10 @@ AvHAIWeapon UTIL_GetWeaponTypeFromEdict(const edict_t* ItemEdict);
|
|||
|
||||
int AITAC_GetNumActivePlayersOnTeam(const AvHTeamNumber Team);
|
||||
int AITAC_GetNumPlayersOfTeamInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 IgnoreClass);
|
||||
int AITAC_GetNumPlayersOfTeamAndClassInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 SearchClass);
|
||||
int AITAC_GetNumPlayersOnTeamOfClass(const AvHTeamNumber Team, const AvHUser3 SearchClass, const edict_t* IgnorePlayer);
|
||||
edict_t* AITAC_GetNearestPlayerOfClassInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 SearchClass);
|
||||
vector<edict_t*> AITAC_GetAllPlayersOfClassInArea(const AvHTeamNumber Team, const Vector SearchLocation, const float SearchRadius, const bool bConsiderPhaseDist, const edict_t* IgnorePlayer, const AvHUser3 SearchClass);
|
||||
|
||||
AvHAIHiveDefinition* AITAC_GetTeamHiveWithTech(const AvHTeamNumber Team, const AvHMessageID Tech);
|
||||
bool AITAC_TeamHiveWithTechExists(const AvHTeamNumber Team, const AvHMessageID Tech);
|
||||
|
@ -158,4 +160,12 @@ const vector<AvHAIHiveDefinition*> AITAC_GetAllHives();
|
|||
|
||||
bool AITAC_AnyPlayerOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius);
|
||||
|
||||
bool AITAC_IsAlienBuilderNeeded(AvHAIPlayer* pBot);
|
||||
bool AITAC_IsAlienCapperNeeded(AvHAIPlayer* pBot);
|
||||
bool AITAC_IsAlienHarasserNeeded(AvHAIPlayer* pBot);
|
||||
|
||||
bool AITAC_ShouldBotBuildHive(AvHAIPlayer* pBot, AvHAIHiveDefinition** EligibleHive);
|
||||
|
||||
AvHAIDeployableStructureType AITAC_GetNextMissingUpgradeChamberForTeam(AvHTeamNumber Team);
|
||||
|
||||
#endif
|
|
@ -581,71 +581,23 @@ bool AITASK_IsAlienBuildTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
|||
{
|
||||
if (!Task) { return false; }
|
||||
|
||||
if (!Task->TaskLocation) { return false; }
|
||||
if (vIsZero(Task->TaskLocation)) { return false; }
|
||||
|
||||
if (Task->StructureType == STRUCTURE_NONE) { return false; }
|
||||
|
||||
if (pBot->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING) { return true; }
|
||||
|
||||
if (pBot->ActiveBuildInfo.NumAttempts >= 3) { return false; }
|
||||
if (pBot->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_FAILED && pBot->ActiveBuildInfo.NumAttempts >= 3) { return false; }
|
||||
|
||||
if (!FNullEnt(Task->TaskTarget) && !UTIL_IsBuildableStructureStillReachable(pBot, Task->TaskTarget)) { return false; }
|
||||
if (pBot->ActiveBuildInfo.LinkedStructure && (UTIL_StructureIsFullyBuilt(pBot->ActiveBuildInfo.LinkedStructure->edict) || !UTIL_IsBuildableStructureStillReachable(pBot, pBot->ActiveBuildInfo.LinkedStructure->edict))) { return false; }
|
||||
|
||||
if (Task->StructureType == STRUCTURE_ALIEN_HIVE)
|
||||
{
|
||||
if (gpGlobals->time - Task->LastBuildAttemptTime < 1.0f) { return true; }
|
||||
|
||||
const AvHAIHiveDefinition* HiveIndex = AITAC_GetHiveNearestLocation(Task->TaskLocation);
|
||||
|
||||
if (!HiveIndex) { return false; }
|
||||
|
||||
if (HiveIndex->Status != HIVE_STATUS_UNBUILT) { return false; }
|
||||
|
||||
edict_t* OtherHiveBuilder = nullptr;
|
||||
AvHAIPlayer* OtherHiveBuilderBot = GetFirstBotWithBuildTask(pBot->Player->GetTeam(), STRUCTURE_ALIEN_HIVE, pBot->Edict);
|
||||
|
||||
if (OtherHiveBuilderBot)
|
||||
{
|
||||
OtherHiveBuilder = OtherHiveBuilderBot->Edict;
|
||||
}
|
||||
|
||||
if (!FNullEnt(OtherHiveBuilder) && GetPlayerResources(OtherHiveBuilder) > GetPlayerResources(pBot->Edict)) { return false; }
|
||||
|
||||
edict_t* OtherGorge = AITAC_GetNearestPlayerOfClassInArea(pBot->Player->GetTeam(), HiveIndex->Location, UTIL_MetresToGoldSrcUnits(10.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2);
|
||||
|
||||
if (!FNullEnt(OtherGorge) && GetPlayerResources(OtherGorge) > pBot->Player->GetResources())
|
||||
{
|
||||
char buf[512];
|
||||
sprintf(buf, "I won't drop hive, %s can do it", STRING(OtherGorge->v.netname));
|
||||
BotSay(pBot, true, 1.0f, buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
DeployableSearchFilter EnemyFilter;
|
||||
EnemyFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY);
|
||||
EnemyFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
|
||||
EnemyFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f);
|
||||
|
||||
// Marines have built a phase gate and/or turret factory in the hive
|
||||
if (AITAC_DeployableExistsAtLocation(HiveIndex->Location, &EnemyFilter))
|
||||
{
|
||||
string LocationName;
|
||||
char buf[512];
|
||||
|
||||
if (GetNearestMapLocationAtPoint(HiveIndex->Location, LocationName))
|
||||
{
|
||||
sprintf(buf, "We need to clear %s before I can build the hive", LocationName.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(buf, "We need to clear the hive before I can build it");
|
||||
}
|
||||
|
||||
BotSay(pBot, true, 1.0f, buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Task->StructureType == STRUCTURE_ALIEN_RESTOWER)
|
||||
|
@ -658,54 +610,13 @@ bool AITASK_IsAlienBuildTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
|||
{
|
||||
if (ResNodeIndex->OwningTeam != pBot->Player->GetTeam()) { return false; } // Node has been capped by the enemy, no longer relevant
|
||||
|
||||
if (!IsPlayerGorge(pBot->Edict)) { return false; } // Don't evolve into a gorge just to finish building an already-placed structure
|
||||
|
||||
if (UTIL_StructureIsFullyBuilt(ResNodeIndex->ActiveTowerEntity)) { return false; } // Don't bother if it's already completed
|
||||
|
||||
if (vDist2DSq(pBot->Edict->v.origin, ResNodeIndex->Location) > sqrf(UTIL_MetresToGoldSrcUnits(10.0f))) { return false; } // Only bother finishing the build if you're close enough
|
||||
|
||||
if (FNullEnt(Task->TaskTarget))
|
||||
{
|
||||
Task->TaskTarget = ResNodeIndex->ActiveTowerEntity;
|
||||
}
|
||||
}
|
||||
|
||||
edict_t* OtherGorge = AITAC_GetNearestPlayerOfClassInArea(pBot->Player->GetTeam(), Task->TaskLocation, UTIL_MetresToGoldSrcUnits(5.0f), false, pBot->Edict, AVH_USER3_ALIEN_PLAYER2);
|
||||
|
||||
// Check if another player is planning to cap the res node. If they are closer, and either the tower is already placed or they have enough res to place the tower, then move on and do something else
|
||||
if (!FNullEnt(OtherGorge))
|
||||
{
|
||||
if (vDist2DSq(OtherGorge->v.origin, Task->TaskLocation) < vDist2DSq(pBot->Edict->v.origin, Task->TaskLocation) && (!FNullEnt(Task->TaskTarget) || (GetPlayerResources(OtherGorge) >= BALANCE_VAR(kResourceTowerCost))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DeployableSearchFilter StructureFilter;
|
||||
StructureFilter.DeployableTypes = Task->StructureType;
|
||||
StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
|
||||
StructureFilter.DeployableTeam = pBot->Player->GetTeam();
|
||||
|
||||
// Don't build more if we've already got quite a few in the immediate vicinity. Helps prevent structure spam
|
||||
if (AITAC_GetNumDeployablesNearLocation(Task->TaskLocation, &StructureFilter) >= 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FNullEnt(Task->TaskTarget))
|
||||
{
|
||||
if ((Task->TaskTarget->v.effects & EF_NODRAW) || (Task->TaskTarget->v.deadflag == DEAD_DEAD)) { return false; }
|
||||
return !UTIL_StructureIsFullyBuilt(Task->TaskTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Task->StructureType == STRUCTURE_ALIEN_RESTOWER) { return true; }
|
||||
|
||||
return UTIL_GetNavAreaAtLocation(Task->TaskLocation) == SAMPLE_POLYAREA_GROUND;
|
||||
}
|
||||
return UTIL_GetNavAreaAtLocation(Task->TaskLocation) == SAMPLE_POLYAREA_GROUND;
|
||||
}
|
||||
|
||||
bool AITASK_IsAlienCapResNodeTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
||||
|
@ -1261,269 +1172,172 @@ void BotProgressMineStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void BotProgressReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
||||
{
|
||||
if (FNullEnt(Task->TaskTarget)) { return; }
|
||||
|
||||
if (!FNullEnt(Task->TaskSecondaryTarget))
|
||||
if (pBot->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING) { return; }
|
||||
|
||||
// We had a go, whether it succeeded or not we should try a new location
|
||||
if (pBot->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_FAILED || pBot->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_SUCCESS)
|
||||
{
|
||||
if (UTIL_StructureIsFullyBuilt(Task->TaskSecondaryTarget))
|
||||
Task->TaskLocation = ZERO_VECTOR;
|
||||
pBot->ActiveBuildInfo.BuildStatus = BUILD_ATTEMPT_NONE;
|
||||
}
|
||||
|
||||
AvHTeamNumber BotTeam = pBot->Player->GetTeam();
|
||||
|
||||
Vector ReinforceLocation = Task->TaskTarget->v.origin;
|
||||
float SearchRadius = UTIL_MetresToGoldSrcUnits(5.0f);
|
||||
|
||||
if (Task->StructureType == STRUCTURE_ALIEN_HIVE)
|
||||
{
|
||||
AvHAIHiveDefinition* HiveToReinforce = AITAC_GetHiveFromEdict(Task->TaskTarget);
|
||||
|
||||
if (HiveToReinforce)
|
||||
{
|
||||
Task->TaskSecondaryTarget = nullptr;
|
||||
Task->BuildAttempts = 0;
|
||||
Task->bIsWaitingForBuildLink = false;
|
||||
Task->TaskLocation = g_vecZero;
|
||||
ReinforceLocation = HiveToReinforce->FloorLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsPlayerInUseRange(pBot->Edict, Task->TaskSecondaryTarget))
|
||||
{
|
||||
BotUseObject(pBot, Task->TaskSecondaryTarget, true);
|
||||
if (vDist2DSq(pBot->Edict->v.origin, Task->TaskSecondaryTarget->v.origin) > sqrf(60.0f))
|
||||
{
|
||||
MoveDirectlyTo(pBot, Task->TaskSecondaryTarget->v.origin);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MoveTo(pBot, Task->TaskSecondaryTarget->v.origin, MOVESTYLE_NORMAL);
|
||||
|
||||
return;
|
||||
}
|
||||
SearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
|
||||
}
|
||||
|
||||
if (gpGlobals->time - Task->LastBuildAttemptTime < 1.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Task->bIsWaitingForBuildLink)
|
||||
{
|
||||
Task->TaskLocation = g_vecZero;
|
||||
Task->bIsWaitingForBuildLink = false;
|
||||
}
|
||||
|
||||
AvHMessageID HiveTechOne = CONFIG_GetHiveTechAtIndex(0);
|
||||
AvHMessageID HiveTechTwo = CONFIG_GetHiveTechAtIndex(1);
|
||||
AvHMessageID HiveTechThree = CONFIG_GetHiveTechAtIndex(2);
|
||||
|
||||
AvHAIDeployableStructureType ChamberTypeOne = UTIL_GetChamberTypeForHiveTech(HiveTechOne);
|
||||
AvHAIDeployableStructureType ChamberTypeTwo = UTIL_GetChamberTypeForHiveTech(HiveTechTwo);
|
||||
AvHAIDeployableStructureType ChamberTypeThree = UTIL_GetChamberTypeForHiveTech(HiveTechThree);
|
||||
|
||||
bool bActiveHiveWithoutTechExists = AITAC_TeamHiveWithTechExists(pBot->Player->GetTeam(), MESSAGE_NULL);
|
||||
bool bActiveHiveWithTechOneExists = AITAC_TeamHiveWithTechExists(pBot->Player->GetTeam(), HiveTechOne);
|
||||
bool bActiveHiveWithTechTwoExists = AITAC_TeamHiveWithTechExists(pBot->Player->GetTeam(), HiveTechTwo);
|
||||
bool bActiveHiveWithTechThreeExists = AITAC_TeamHiveWithTechExists(pBot->Player->GetTeam(), HiveTechThree);
|
||||
AvHAIDeployableStructureType NextStructure = AITAC_GetNextMissingUpgradeChamberForTeam(BotTeam);
|
||||
|
||||
DeployableSearchFilter StructureFilter;
|
||||
StructureFilter.DeployableTypes = ChamberTypeOne;
|
||||
StructureFilter.DeployableTeam = pBot->Player->GetTeam();
|
||||
StructureFilter.DeployableTeam = BotTeam;
|
||||
StructureFilter.MaxSearchRadius = SearchRadius;
|
||||
|
||||
int NumHiveTechOne = AITAC_GetNumDeployablesNearLocation(ZERO_VECTOR, &StructureFilter);
|
||||
StructureFilter.DeployableTypes = ChamberTypeTwo;
|
||||
int NumHiveTechTwo = AITAC_GetNumDeployablesNearLocation(ZERO_VECTOR, &StructureFilter);
|
||||
StructureFilter.DeployableTypes = ChamberTypeThree;
|
||||
int NumHiveTechThree = AITAC_GetNumDeployablesNearLocation(ZERO_VECTOR, &StructureFilter);
|
||||
|
||||
if (Task->StructureType == STRUCTURE_NONE)
|
||||
if (NextStructure == STRUCTURE_NONE)
|
||||
{
|
||||
if (bActiveHiveWithoutTechExists)
|
||||
StructureFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER;
|
||||
|
||||
int NumOCs = AITAC_GetNumDeployablesNearLocation(ReinforceLocation, &StructureFilter);
|
||||
|
||||
if (NumOCs < 3)
|
||||
{
|
||||
if (!bActiveHiveWithTechOneExists)
|
||||
{
|
||||
Task->StructureType = ChamberTypeOne;
|
||||
}
|
||||
else if (!bActiveHiveWithTechTwoExists)
|
||||
{
|
||||
Task->StructureType = ChamberTypeTwo;
|
||||
}
|
||||
else if (!bActiveHiveWithTechThreeExists)
|
||||
{
|
||||
Task->StructureType = ChamberTypeThree;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bActiveHiveWithTechOneExists && NumHiveTechOne < 3)
|
||||
{
|
||||
Task->StructureType = ChamberTypeOne;
|
||||
}
|
||||
else if (bActiveHiveWithTechTwoExists && NumHiveTechTwo < 3)
|
||||
{
|
||||
Task->StructureType = ChamberTypeTwo;
|
||||
}
|
||||
else if (bActiveHiveWithTechThreeExists && NumHiveTechThree < 3)
|
||||
{
|
||||
Task->StructureType = ChamberTypeThree;
|
||||
}
|
||||
else
|
||||
{
|
||||
Task->StructureType = STRUCTURE_ALIEN_OFFENCECHAMBER;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool bCanBuildChamberTypeOne = bActiveHiveWithoutTechExists || bActiveHiveWithTechOneExists;
|
||||
bool bCanBuildChamberTypeTwo = bActiveHiveWithoutTechExists || bActiveHiveWithTechTwoExists;
|
||||
bool bCanBuildChamberTypeThree = bActiveHiveWithoutTechExists || bActiveHiveWithTechThreeExists;
|
||||
|
||||
StructureFilter.DeployableTypes = Task->StructureType;
|
||||
StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
|
||||
|
||||
int NumChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter);
|
||||
int NumDesiredChambers = (Task->StructureType == STRUCTURE_ALIEN_OFFENCECHAMBER || Task->StructureType == STRUCTURE_ALIEN_DEFENCECHAMBER) ? 2 : 1;
|
||||
|
||||
if (Task->StructureType != STRUCTURE_ALIEN_OFFENCECHAMBER)
|
||||
{
|
||||
// The idea here is the gorge builds however many chambers at this hive/RT are needed to meet the minimum
|
||||
// reinforce requirements, or ensure 3 of each upgrade chamber are built, whichever is more.
|
||||
// e.g. normally they only build 1 MC/SC per hive/RT, but will build 3 if needed to ensure a min 3 of each chamber
|
||||
|
||||
int Deficit = 0;
|
||||
|
||||
if (Task->StructureType == ChamberTypeOne)
|
||||
{
|
||||
Deficit = clampi((3 - NumHiveTechOne), 0, 3);
|
||||
}
|
||||
|
||||
if (Task->StructureType == ChamberTypeTwo)
|
||||
{
|
||||
Deficit = clampi((3 - NumHiveTechTwo), 0, 3);
|
||||
}
|
||||
|
||||
if (Task->StructureType == ChamberTypeThree)
|
||||
{
|
||||
Deficit = clampi((3 - NumHiveTechThree), 0, 3);
|
||||
}
|
||||
|
||||
Deficit += NumChambers;
|
||||
|
||||
NumDesiredChambers = imaxi(Deficit, NumDesiredChambers);
|
||||
}
|
||||
|
||||
if (NumChambers >= NumDesiredChambers)
|
||||
{
|
||||
bool bChosenNextChamber = false;
|
||||
|
||||
if (!bActiveHiveWithoutTechExists)
|
||||
{
|
||||
StructureFilter.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER;
|
||||
|
||||
int NumOffenceChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter);
|
||||
|
||||
if (NumOffenceChambers < 2)
|
||||
{
|
||||
Task->StructureType = STRUCTURE_ALIEN_OFFENCECHAMBER;
|
||||
bChosenNextChamber = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bChosenNextChamber && bCanBuildChamberTypeOne)
|
||||
{
|
||||
StructureFilter.DeployableTypes = ChamberTypeOne;
|
||||
|
||||
int NumDefenceChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter);
|
||||
|
||||
if (NumDefenceChambers < 2)
|
||||
{
|
||||
Task->StructureType = ChamberTypeOne;
|
||||
bChosenNextChamber = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bChosenNextChamber && bCanBuildChamberTypeTwo)
|
||||
{
|
||||
StructureFilter.DeployableTypes = ChamberTypeTwo;
|
||||
|
||||
int NumMoveChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter);
|
||||
|
||||
if (NumMoveChambers < 1)
|
||||
{
|
||||
Task->StructureType = ChamberTypeTwo;
|
||||
bChosenNextChamber = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bChosenNextChamber && bCanBuildChamberTypeThree)
|
||||
{
|
||||
StructureFilter.DeployableTypes = ChamberTypeThree;
|
||||
|
||||
int NumSensoryChambers = AITAC_GetNumDeployablesNearLocation(Task->TaskTarget->v.origin, &StructureFilter);
|
||||
|
||||
if (NumSensoryChambers < 1)
|
||||
{
|
||||
Task->StructureType = ChamberTypeThree;
|
||||
bChosenNextChamber = true;
|
||||
}
|
||||
}
|
||||
if (!bChosenNextChamber) { return; }
|
||||
}
|
||||
|
||||
if (Task->TaskLocation != g_vecZero)
|
||||
{
|
||||
dtPolyRef Poly = UTIL_GetNavAreaAtLocation(BaseNavProfiles[STRUCTURE_BASE_NAV_PROFILE], Task->TaskLocation);
|
||||
|
||||
if (Poly != SAMPLE_POLYAREA_GROUND)
|
||||
{
|
||||
Task->TaskLocation = g_vecZero;
|
||||
NextStructure = STRUCTURE_ALIEN_OFFENCECHAMBER;
|
||||
}
|
||||
}
|
||||
|
||||
if (vIsZero(Task->TaskLocation))
|
||||
if (NextStructure == STRUCTURE_NONE)
|
||||
{
|
||||
AvHAIDeployableStructureType ReinforcedStructure = GetStructureTypeFromEdict(Task->TaskTarget);
|
||||
|
||||
Vector TargetLocation = (ReinforcedStructure == STRUCTURE_ALIEN_HIVE) ? UTIL_GetFloorUnderEntity(Task->TaskTarget) : Task->TaskTarget->v.origin;
|
||||
|
||||
Vector BuildLocation = FindClosestNavigablePointToDestination(BaseNavProfiles[MARINE_BASE_NAV_PROFILE], AITAC_GetTeamStartingLocation(GetGameRules()->GetTeamANumber()), TargetLocation, UTIL_MetresToGoldSrcUnits(50.0f));
|
||||
|
||||
if (BuildLocation != g_vecZero)
|
||||
if (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_DEFENSE_CHAMBER))
|
||||
{
|
||||
float currDist = vDist2D(BuildLocation, TargetLocation);
|
||||
StructureFilter.DeployableTypes = STRUCTURE_ALIEN_DEFENCECHAMBER;
|
||||
|
||||
float MaxDist = fmaxf(UTIL_MetresToGoldSrcUnits(1.0f), (UTIL_MetresToGoldSrcUnits(5.0f) - currDist));
|
||||
int NumDCs = AITAC_GetNumDeployablesNearLocation(ReinforceLocation, &StructureFilter);
|
||||
|
||||
Task->TaskLocation = UTIL_GetRandomPointOnNavmeshInRadius(BaseNavProfiles[GORGE_BASE_NAV_PROFILE], BuildLocation, MaxDist);
|
||||
if (NumDCs < 2)
|
||||
{
|
||||
NextStructure = STRUCTURE_ALIEN_DEFENCECHAMBER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (NextStructure == STRUCTURE_NONE)
|
||||
{
|
||||
if (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_MOVEMENT_CHAMBER))
|
||||
{
|
||||
StructureFilter.DeployableTypes = STRUCTURE_ALIEN_MOVEMENTCHAMBER;
|
||||
|
||||
int NumMCs = AITAC_GetNumDeployablesNearLocation(ReinforceLocation, &StructureFilter);
|
||||
|
||||
if (NumMCs < 1)
|
||||
{
|
||||
NextStructure = STRUCTURE_ALIEN_MOVEMENTCHAMBER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (NextStructure == STRUCTURE_NONE)
|
||||
{
|
||||
if (AITAC_TeamHiveWithTechExists(BotTeam, ALIEN_BUILD_SENSORY_CHAMBER))
|
||||
{
|
||||
StructureFilter.DeployableTypes = STRUCTURE_ALIEN_SENSORYCHAMBER;
|
||||
|
||||
int NumSCs = AITAC_GetNumDeployablesNearLocation(ReinforceLocation, &StructureFilter);
|
||||
|
||||
if (NumSCs < 1)
|
||||
{
|
||||
NextStructure = STRUCTURE_ALIEN_SENSORYCHAMBER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (NextStructure != STRUCTURE_NONE)
|
||||
{
|
||||
if (vIsZero(Task->TaskLocation))
|
||||
{
|
||||
Vector NewLoc = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ReinforceLocation, SearchRadius);
|
||||
|
||||
if (vIsZero(NewLoc))
|
||||
{
|
||||
NewLoc = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(GORGE_BASE_NAV_PROFILE), ReinforceLocation, SearchRadius);
|
||||
}
|
||||
|
||||
Task->TaskLocation = NewLoc;
|
||||
}
|
||||
|
||||
if (vIsZero(Task->TaskLocation)) { return; }
|
||||
}
|
||||
|
||||
int ResRequired = UTIL_GetCostOfStructureType(Task->StructureType);
|
||||
|
||||
if (!IsPlayerGorge(pBot->Edict))
|
||||
{
|
||||
ResRequired += BALANCE_VAR(kGorgeCost);
|
||||
}
|
||||
|
||||
if (pBot->Player->GetResources() < ResRequired)
|
||||
{
|
||||
if (!IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB))
|
||||
if (!vIsZero(Task->TaskLocation))
|
||||
{
|
||||
DeployableSearchFilter EnemyStuff;
|
||||
EnemyStuff.DeployableTeam = AIMGR_GetEnemyTeam(pBot->Player->GetTeam());
|
||||
EnemyStuff.DeployableTypes = SEARCH_ALL_STRUCTURES;
|
||||
EnemyStuff.ReachabilityTeam = pBot->Player->GetTeam();
|
||||
EnemyStuff.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
|
||||
EnemyStuff.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
|
||||
|
||||
AvHAIBuildableStructure* NearestEnemyStructure = AITAC_FindClosestDeployableToLocation(Task->TaskTarget->v.origin, &EnemyStuff);
|
||||
|
||||
if (NearestEnemyStructure)
|
||||
float ResourceCost = UTIL_GetCostOfStructureType(NextStructure);
|
||||
if (!IsPlayerGorge(pBot->Edict))
|
||||
{
|
||||
BotAttackNonPlayerTarget(pBot, NearestEnemyStructure->edict);
|
||||
ResourceCost += BALANCE_VAR(kGorgeCost);
|
||||
}
|
||||
|
||||
if (pBot->Player->GetResources() >= ResourceCost)
|
||||
{
|
||||
BotAlienPlaceChamber(pBot, Task->TaskLocation, NextStructure);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BotGuardLocation(pBot, Task->TaskLocation);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have nothing to build, or we don't have enough resources yet, see if there's any unfinished structures we can finish off
|
||||
if (IsPlayerGorge(pBot->Edict))
|
||||
{
|
||||
DeployableSearchFilter UnfinishedFilter;
|
||||
UnfinishedFilter.DeployableTeam = BotTeam;
|
||||
UnfinishedFilter.ExcludeStatusFlags = STRUCTURE_STATUS_COMPLETED;
|
||||
UnfinishedFilter.ReachabilityTeam = BotTeam;
|
||||
UnfinishedFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
|
||||
UnfinishedFilter.DeployableTypes = SEARCH_ALL_ALIEN_STRUCTURES;
|
||||
UnfinishedFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
|
||||
|
||||
BotAlienPlaceChamber(pBot, Task->TaskLocation, Task->StructureType);
|
||||
AvHAIBuildableStructure* UnfinishedStructure = AITAC_FindClosestDeployableToLocation(ReinforceLocation, &UnfinishedFilter);
|
||||
|
||||
if (UnfinishedStructure)
|
||||
{
|
||||
AIPlayerBuildStructure(pBot, UnfinishedStructure->edict);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We can't build anything, see if there's anything to attack nearby
|
||||
if (!IsPlayerGorge(pBot->Edict) || PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB))
|
||||
{
|
||||
DeployableSearchFilter EnemyStructureFilter;
|
||||
EnemyStructureFilter.DeployableTeam = AIMGR_GetEnemyTeam(BotTeam);
|
||||
EnemyStructureFilter.DeployableTypes = SEARCH_ALL_STRUCTURES;
|
||||
EnemyStructureFilter.ReachabilityTeam = BotTeam;
|
||||
EnemyStructureFilter.ReachabilityFlags = pBot->BotNavInfo.NavProfile.ReachabilityFlag;
|
||||
EnemyStructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f);
|
||||
|
||||
AvHAIBuildableStructure* EnemyStructure = AITAC_FindClosestDeployableToLocation(ReinforceLocation, &EnemyStructureFilter);
|
||||
|
||||
if (EnemyStructure)
|
||||
{
|
||||
BotAttackNonPlayerTarget(pBot, EnemyStructure->edict);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BotGuardLocation(pBot, ReinforceLocation);
|
||||
|
||||
|
||||
}
|
||||
|
@ -1582,46 +1396,48 @@ void AIPlayerBuildStructure(AvHAIPlayer* pBot, edict_t* BuildTarget)
|
|||
|
||||
if (IsPlayerInUseRange(pBot->Edict, BuildTarget))
|
||||
{
|
||||
// If we were ducking before then keep ducking
|
||||
if (pBot->Edict->v.oldbuttons & IN_DUCK)
|
||||
{
|
||||
pBot->Button |= IN_DUCK;
|
||||
}
|
||||
|
||||
BotUseObject(pBot, BuildTarget, true);
|
||||
|
||||
// Haven't started building, maybe not quite looking at the right angle
|
||||
if (pBot->Edict->v.weaponmodel != 0)
|
||||
if (IsPlayerMarine(pBot->Edict))
|
||||
{
|
||||
if (vDist2DSq(pBot->Edict->v.origin, BuildTarget->v.origin) > sqrf(60.0f))
|
||||
// If we were ducking before then keep ducking
|
||||
if (pBot->Edict->v.oldbuttons & IN_DUCK)
|
||||
{
|
||||
MoveDirectlyTo(pBot, BuildTarget->v.origin);
|
||||
pBot->Button |= IN_DUCK;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector NewViewPoint = UTIL_GetRandomPointInBoundingBox(BuildTarget->v.absmin, BuildTarget->v.absmax);
|
||||
|
||||
BotLookAt(pBot, NewViewPoint);
|
||||
if (pBot->Edict->v.weaponmodel != 0)
|
||||
{
|
||||
|
||||
if (vDist2DSq(pBot->Edict->v.origin, BuildTarget->v.origin) > sqrf(60.0f))
|
||||
{
|
||||
MoveDirectlyTo(pBot, BuildTarget->v.origin);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector NewViewPoint = UTIL_GetRandomPointInBoundingBox(BuildTarget->v.absmin, BuildTarget->v.absmax);
|
||||
|
||||
BotLookAt(pBot, NewViewPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
// Might need to duck if it's an infantry portal
|
||||
if (vDist2DSq(pBot->Edict->v.origin, BuildTarget->v.origin) < sqrf(max_player_use_reach))
|
||||
{
|
||||
// Might need to duck if it's an infantry portal
|
||||
if (vDist2DSq(pBot->Edict->v.origin, BuildTarget->v.origin) < sqrf(max_player_use_reach))
|
||||
if (BuildTarget->v.origin > pBot->Edict->v.origin)
|
||||
{
|
||||
if (BuildTarget->v.origin > pBot->Edict->v.origin)
|
||||
{
|
||||
BotJump(pBot);
|
||||
}
|
||||
else
|
||||
{
|
||||
pBot->Button |= IN_DUCK;
|
||||
}
|
||||
|
||||
BotJump(pBot);
|
||||
}
|
||||
else
|
||||
{
|
||||
pBot->Button |= IN_DUCK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MoveTo(pBot, BuildTarget->v.origin, MOVESTYLE_NORMAL);
|
||||
|
@ -1777,6 +1593,12 @@ void BotProgressAttackTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
|||
return;
|
||||
}
|
||||
|
||||
if (IsPlayerGorge(pBot->Edict) && !PlayerHasWeapon(pBot->Player, WEAPON_GORGE_BILEBOMB))
|
||||
{
|
||||
BotEvolveLifeform(pBot, pBot->Edict->v.origin, ALIEN_LIFEFORM_ONE);
|
||||
return;
|
||||
}
|
||||
|
||||
AvHAIWeapon Weapon = WEAPON_INVALID;
|
||||
|
||||
if (IsPlayerMarine(pBot->Edict))
|
||||
|
@ -2038,6 +1860,12 @@ void AlienProgressBuildHiveTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
|||
|
||||
void AlienProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
||||
{
|
||||
// We tried and failed to place the structure
|
||||
if (pBot->ActiveBuildInfo.BuildStatus == BUILD_ATTEMPT_PENDING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Task->StructureType == STRUCTURE_ALIEN_HIVE)
|
||||
{
|
||||
AlienProgressBuildHiveTask(pBot, Task);
|
||||
|
@ -2046,19 +1874,21 @@ void AlienProgressBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task)
|
|||
|
||||
if (pBot->ActiveBuildInfo.LinkedStructure)
|
||||
{
|
||||
if (UTIL_StructureIsFullyBuilt(pBot->ActiveBuildInfo.LinkedStructure->edict)) { return; }
|
||||
edict_t* LinkedEdict = pBot->ActiveBuildInfo.LinkedStructure->edict;
|
||||
|
||||
if (IsPlayerInUseRange(pBot->Edict, Task->TaskTarget))
|
||||
if (UTIL_StructureIsFullyBuilt(LinkedEdict)) { return; }
|
||||
|
||||
if (IsPlayerInUseRange(pBot->Edict, LinkedEdict))
|
||||
{
|
||||
BotUseObject(pBot, Task->TaskTarget, true);
|
||||
if (vDist2DSq(pBot->Edict->v.origin, Task->TaskTarget->v.origin) > sqrf(60.0f))
|
||||
BotUseObject(pBot, LinkedEdict, true);
|
||||
if (vDist2DSq(pBot->Edict->v.origin, LinkedEdict->v.origin) > sqrf(60.0f))
|
||||
{
|
||||
MoveDirectlyTo(pBot, Task->TaskTarget->v.origin);
|
||||
MoveDirectlyTo(pBot, LinkedEdict->v.origin);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MoveTo(pBot, Task->TaskTarget->v.origin, MOVESTYLE_NORMAL);
|
||||
MoveTo(pBot, LinkedEdict->v.origin, MOVESTYLE_NORMAL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -3231,10 +3061,9 @@ void AITASK_SetCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, const Av
|
|||
|
||||
AvHAIDeployableStructureType NodeStructureType = (IsPlayerMarine(pBot->Edict)) ? STRUCTURE_MARINE_RESTOWER : STRUCTURE_ALIEN_RESTOWER;
|
||||
|
||||
if (Task->TaskType == TASK_CAP_RESNODE && Task->TaskLocation == NodeRef->Location)
|
||||
if (Task->TaskType == TASK_CAP_RESNODE && Task->TaskTarget == NodeRef->ResourceEdict)
|
||||
{
|
||||
Task->bTaskIsUrgent = bIsUrgent;
|
||||
Task->TaskTarget = NodeRef->ActiveTowerEntity;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3245,11 +3074,7 @@ void AITASK_SetCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, const Av
|
|||
Task->TaskType = TASK_CAP_RESNODE;
|
||||
Task->StructureType = NodeStructureType;
|
||||
Task->TaskLocation = (!vIsZero(WaitLocation)) ? WaitLocation : NodeRef->Location;
|
||||
|
||||
if (!FNullEnt(NodeRef->ActiveTowerEntity))
|
||||
{
|
||||
Task->TaskTarget = NodeRef->ActiveTowerEntity;
|
||||
}
|
||||
Task->TaskTarget = NodeRef->ResourceEdict;
|
||||
|
||||
Task->bTaskIsUrgent = bIsUrgent;
|
||||
}
|
||||
|
@ -3389,6 +3214,7 @@ void AITASK_SetReinforceStructureTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task,
|
|||
|
||||
Task->TaskType = TASK_REINFORCE_STRUCTURE;
|
||||
Task->TaskTarget = Target;
|
||||
Task->StructureType = GetStructureTypeFromEdict(Target);
|
||||
Task->bTaskIsUrgent = bIsUrgent;
|
||||
}
|
||||
|
||||
|
|
|
@ -526,6 +526,16 @@ Vector UTIL_GetGrenadeThrowTarget(edict_t* Player, const Vector TargetLocation,
|
|||
}
|
||||
}
|
||||
|
||||
AvHAIWeapon BotAlienChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target)
|
||||
{
|
||||
if (FNullEnt(target))
|
||||
{
|
||||
return UTIL_GetPlayerPrimaryWeapon(pBot->Player);
|
||||
}
|
||||
|
||||
return BotAlienChooseBestWeaponForStructure(pBot, target);
|
||||
}
|
||||
|
||||
AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target)
|
||||
{
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ AvHAIWeapon BotAlienChooseBestWeaponForStructure(AvHAIPlayer* pBot, edict_t* tar
|
|||
|
||||
// Helper function to pick the best weapon for any given situation and target type.
|
||||
AvHAIWeapon BotMarineChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target);
|
||||
AvHAIWeapon BotAlienChooseBestWeapon(AvHAIPlayer* pBot, edict_t* target);
|
||||
|
||||
AvHAIWeapon FadeGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target);
|
||||
AvHAIWeapon OnosGetBestWeaponForCombatTarget(AvHAIPlayer* pBot, edict_t* Target);
|
||||
|
|
|
@ -1535,6 +1535,57 @@ BOOL AvHGamerules::ClientCommand( CBasePlayer *pPlayer, const char *pcmd )
|
|||
|
||||
theSuccess = true;
|
||||
}
|
||||
else if (FStrEq(pcmd, "getaliencomp"))
|
||||
{
|
||||
vector<AvHAIPlayer*> AlienPlayers = AIMGR_GetAIPlayersOnTeam(TEAM_TWO);
|
||||
|
||||
int NumBuilders = 0;
|
||||
int NumCappers = 0;
|
||||
int NumHarassers = 0;
|
||||
int NumAssault = 0;
|
||||
|
||||
for (auto it = AlienPlayers.begin(); it != AlienPlayers.end(); it++)
|
||||
{
|
||||
AvHAIPlayer* NewCapper = (*it);
|
||||
|
||||
if (NewCapper)
|
||||
{
|
||||
switch (NewCapper->BotRole)
|
||||
{
|
||||
case BOT_ROLE_BUILDER:
|
||||
NumBuilders++;
|
||||
break;
|
||||
case BOT_ROLE_FIND_RESOURCES:
|
||||
NumCappers++;
|
||||
break;
|
||||
case BOT_ROLE_HARASS:
|
||||
NumHarassers++;
|
||||
break;
|
||||
case BOT_ROLE_ASSAULT:
|
||||
NumAssault++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char buf[32];
|
||||
|
||||
sprintf(buf, "Builders: %d\n", NumBuilders);
|
||||
UTIL_SayText(buf, theAvHPlayer);
|
||||
|
||||
sprintf(buf, "Cappers: %d\n", NumCappers);
|
||||
UTIL_SayText(buf, theAvHPlayer);
|
||||
|
||||
sprintf(buf, "Harassers: %d\n", NumHarassers);
|
||||
UTIL_SayText(buf, theAvHPlayer);
|
||||
|
||||
sprintf(buf, "Assault: %d\n", NumAssault);
|
||||
UTIL_SayText(buf, theAvHPlayer);
|
||||
|
||||
theSuccess = true;
|
||||
}
|
||||
else if (FStrEq(pcmd, "testresearchavailable"))
|
||||
{
|
||||
AvHTeam* PlayerTeam = GetGameRules()->GetTeam(theAvHPlayer->GetTeam());
|
||||
|
|
Loading…
Reference in a new issue