diff --git a/main/source/mod/AIPlayers/AvHAICommander.cpp b/main/source/mod/AIPlayers/AvHAICommander.cpp index f248ef9f..c3d2c1c6 100644 --- a/main/source/mod/AIPlayers/AvHAICommander.cpp +++ b/main/source/mod/AIPlayers/AvHAICommander.cpp @@ -479,6 +479,11 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) int NumPlayersOnTeam = AITAC_GetNumActivePlayersOnTeam(pBot->Player->GetTeam()); int DesiredPlayers = imini(2, (int)ceilf((float)NumPlayersOnTeam *0.5f)); + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); + + Vector TeamStartingLocation = AITAC_GetTeamStartingLocation(BotTeam); + const AvHAIHiveDefinition* SiegedHive = AITAC_GetNearestHiveUnderActiveSiege(pBot->Player->GetTeam(), AITAC_GetCommChairLocation(pBot->Player->GetTeam())); if (SiegedHive) @@ -501,10 +506,7 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) if (AICOMM_ShouldCommanderPrioritiseNodes(pBot)) { - AvHTeamNumber BotTeam = pBot->Player->GetTeam(); - AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); - - Vector TeamStartingLocation = AITAC_GetTeamStartingLocation(BotTeam); + DeployableSearchFilter ResNodeFilter; ResNodeFilter.ReachabilityTeam = pBot->Player->GetTeam(); @@ -570,7 +572,7 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) if (NumAssignedPlayers < DesiredPlayers) { - float ThisDist = vDist2DSq(AITAC_GetCommChairLocation(pBot->Player->GetTeam()), ThisHive->Location); + float ThisDist = vDist2DSq(TeamStartingLocation, ThisHive->Location); if (!EmptyHive || ThisDist < MinDist) { @@ -594,6 +596,61 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) } } + if (!AITAC_ResearchIsComplete(BotTeam, TECH_RESEARCH_PHASETECH)) { return; } + + if (AIMGR_GetTeamType(EnemyTeam) == AVH_CLASS_TYPE_ALIEN) + { + AvHAIHiveDefinition* HiveSiegeOpportunity = nullptr; + + float MinDist = 0.0f; + + for (auto it = Hives.begin(); it != Hives.end(); it++) + { + AvHAIHiveDefinition* ThisHive = (*it); + if (ThisHive->Status == HIVE_STATUS_UNBUILT) { continue; } + + DeployableSearchFilter ExistingSiegeFilter; + ExistingSiegeFilter.DeployableTeam = BotTeam; + ExistingSiegeFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + ExistingSiegeFilter.DeployableTypes = (STRUCTURE_MARINE_PHASEGATE | STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); + ExistingSiegeFilter.ReachabilityTeam = BotTeam; + ExistingSiegeFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; + ExistingSiegeFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(25.0f); + + AvHAIBuildableStructure SiegeStructure = AITAC_FindClosestDeployableToLocation(ThisHive->Location, &ExistingSiegeFilter); + + if (SiegeStructure.IsValid()) + { + HiveSiegeOpportunity = ThisHive; + break; + } + else + { + float ThisDist = vDist2DSq(ThisHive->FloorLocation, TeamStartingLocation); + + if (!HiveSiegeOpportunity || ThisDist < MinDist) + { + HiveSiegeOpportunity = ThisHive; + MinDist = ThisDist; + } + } + } + + if (HiveSiegeOpportunity) + { + for(int i = 0; i < (DesiredPlayers - MinNumAssignedPlayers); i++) + { + edict_t* NewAssignee = AICOMM_GetPlayerWithNoOrderNearestLocation(pBot, HiveSiegeOpportunity->FloorLocation); + + if (!FNullEnt(NewAssignee)) + { + AICOMM_AssignNewPlayerOrder(pBot, NewAssignee, HiveSiegeOpportunity->HiveEdict, ORDERPURPOSE_SIEGE_HIVE); + } + } + } + + } + } edict_t* AICOMM_GetPlayerWithNoOrderNearestLocation(AvHAIPlayer* pBot, Vector SearchLocation) @@ -1843,119 +1900,160 @@ bool AICOMM_PerformNextSiegeHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefinit StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(25.0f); Vector SiegeLocation = ZERO_VECTOR; - AvHAIBuildableStructure ExistingPG; + + StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; + AvHAIBuildableStructure ExistingPG = AITAC_FindClosestDeployableToLocation(HiveToSiege->Location, &StructureFilter); + + StructureFilter.DeployableTypes = (STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY); + AvHAIBuildableStructure ExistingTF = AITAC_FindClosestDeployableToLocation(HiveToSiege->Location, &StructureFilter); + + StructureFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); + AvHAIBuildableStructure ExistingArmoury = AITAC_FindClosestDeployableToLocation(HiveToSiege->Location, &StructureFilter); edict_t* NearestBuilder = nullptr; - if (AITAC_PhaseGatesAvailable(CommanderTeam)) + // We only build one of these at a time, so we don't drop a bunch of structures and then our intrepid sieger gets killed and the aliens nom them all + if (ExistingPG.IsValid()) { - StructureFilter.DeployableTypes = STRUCTURE_MARINE_PHASEGATE; - - ExistingPG = AITAC_FindClosestDeployableToLocation(HiveToSiege->Location, &StructureFilter); - - if (ExistingPG.IsValid()) + if (ExistingPG.IsCompleted()) { SiegeLocation = ExistingPG.Location; - } - - } - - StructureFilter.DeployableTypes = STRUCTURE_MARINE_TURRETFACTORY | STRUCTURE_MARINE_ADVTURRETFACTORY; - - AvHAIBuildableStructure ExistingTF = AITAC_FindClosestDeployableToLocation(HiveToSiege->Location, &StructureFilter); - - if (vIsZero(SiegeLocation)) - { - if (ExistingTF.IsValid()) - { - SiegeLocation = ExistingTF.Location; + NearestBuilder = AITAC_GetClosestPlayerOnTeamWithLOS(CommanderTeam, ExistingPG.Location, UTIL_MetresToGoldSrcUnits(5.0f), pBot->Edict); } else { - NearestBuilder = AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, HiveToSiege->Location, UTIL_MetresToGoldSrcUnits(20.0f)); - - if (FNullEnt(NearestBuilder)) { return false; } - - SiegeLocation = NearestBuilder->v.origin; + // Don't do anything else until we've finished building the phase gate + return false; } } - if (FNullEnt(NearestBuilder)) + if (ExistingTF.IsValid()) { - NearestBuilder = AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, SiegeLocation, UTIL_MetresToGoldSrcUnits(20.0f)); + if (ExistingTF.IsCompleted()) + { + if (vIsZero(SiegeLocation)) + { + SiegeLocation = ExistingTF.Location; + } + + if (FNullEnt(NearestBuilder)) + { + NearestBuilder = AITAC_GetClosestPlayerOnTeamWithLOS(CommanderTeam, ExistingTF.Location, UTIL_MetresToGoldSrcUnits(5.0f), pBot->Edict); + } + } + else + { + // Don't do anything else until we've finished building the turret factory + return false; + } + } + else + { + if (FNullEnt(NearestBuilder)) + { + NearestBuilder = AITAC_GetNearestHiddenPlayerInLocation(CommanderTeam, HiveToSiege->Location, UTIL_MetresToGoldSrcUnits(20.0f)); + } } if (FNullEnt(NearestBuilder)) { return false; } - Vector NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f)); + bool bPhaseGatesAvailable = AITAC_PhaseGatesAvailable(CommanderTeam); - if (vIsZero(NextBuildPosition)) + if (vIsZero(SiegeLocation)) { - NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f)); + SiegeLocation = NearestBuilder->v.origin; + } - if (vIsZero(NextBuildPosition)) + AvHAIDeployableStructureType NextStructure = STRUCTURE_NONE; + + if (!ExistingPG.IsValid() && bPhaseGatesAvailable) + { + NextStructure = STRUCTURE_MARINE_PHASEGATE; + } + else if (!ExistingTF.IsValid()) + { + NextStructure = STRUCTURE_MARINE_TURRETFACTORY; + } + else if (!ExistingArmoury.IsValid()) + { + NextStructure = STRUCTURE_MARINE_ARMOURY; + } + + if (NextStructure != STRUCTURE_NONE) + { + Vector NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f)); + + if (!vIsZero(NextBuildPosition)) { - // Fall-back, this could end up putting the structure in dodgy spots but better than not placing it at all - NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(3.0f)); + bool bSuccess = AICOMM_DeployStructure(pBot, NextStructure, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); + + if (bSuccess) { return true; } } + + NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + + if (!vIsZero(NextBuildPosition)) + { + bool bSuccess = AICOMM_DeployStructure(pBot, NextStructure, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); + + if (bSuccess) { return true; } + } + + NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), SiegeLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + + if (!vIsZero(NextBuildPosition)) + { + bool bSuccess = AICOMM_DeployStructure(pBot, NextStructure, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); + + if (bSuccess) { return true; } + } + + return false; } - if (!ExistingPG.IsValid()) - { - return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); - } + if (!ExistingTF.IsValid()) { return false; } - if (ExistingPG.IsValid() && !(ExistingPG.StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { return false; } - - if (!ExistingTF.IsValid()) - { - if (vDist2DSq(NextBuildPosition, HiveToSiege->Location) > sqrf(UTIL_MetresToGoldSrcUnits(20.0f))) { return true; } - return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); - } - - if (ExistingTF.IsValid() && !(ExistingTF.StructureStatusFlags & STRUCTURE_STATUS_COMPLETED)) { return false; } - - StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY; - StructureFilter.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(10.0f); - - AvHAIBuildableStructure ExistingArmoury = AITAC_FindClosestDeployableToLocation(SiegeLocation, &StructureFilter); - - if (!ExistingArmoury.IsValid()) - { - return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_ARMOURY, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); - } + if ((ExistingTF.StructureStatusFlags & STRUCTURE_STATUS_RESEARCHING)) { return false; } if (ExistingTF.StructureType != STRUCTURE_MARINE_ADVTURRETFACTORY) { 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))) + if (NumSiegeTurrets == 0 || (NumSiegeTurrets < 3 && UTIL_IsStructureElectrified(ExistingTF.edict))) { - SiegeLocation = ExistingTF.Location; + Vector NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ExistingTF.Location, UTIL_MetresToGoldSrcUnits(5.0f)); - NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ExistingTF.Location, UTIL_MetresToGoldSrcUnits(5.0f)); - - if (vIsZero(NextBuildPosition)) + if (!vIsZero(NextBuildPosition) && vDist2DSq(NextBuildPosition, HiveToSiege->Location) <= sqrf(BALANCE_VAR(kSiegeTurretRange))) { - // Reduce radius to avoid putting it on the other side of a wall or something - NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ExistingTF.Location, UTIL_MetresToGoldSrcUnits(3.0f)); + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_SIEGETURRET, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); - if (vIsZero(NextBuildPosition)) - { - // Fall-back, this could end up putting the structure in dodgy spots but better than not placing it at all - NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), ExistingTF.Location, UTIL_MetresToGoldSrcUnits(5.0f)); - } + if (bSuccess) { return true; } + } + + NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ExistingTF.Location, UTIL_MetresToGoldSrcUnits(5.0f)); + + if (!vIsZero(NextBuildPosition) && vDist2DSq(NextBuildPosition, HiveToSiege->Location) <= sqrf(BALANCE_VAR(kSiegeTurretRange))) + { + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_SIEGETURRET, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); + + if (bSuccess) { return true; } + } + + NextBuildPosition = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), ExistingTF.Location, UTIL_MetresToGoldSrcUnits(5.0f)); + + if (!vIsZero(NextBuildPosition) && vDist2DSq(NextBuildPosition, HiveToSiege->Location) <= sqrf(BALANCE_VAR(kSiegeTurretRange))) + { + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_SIEGETURRET, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); + + if (bSuccess) { return true; } } - // Don't put the turret out of siege range - if (vDist2DSq(NextBuildPosition, HiveToSiege->Location) > sqrf(kSiegeTurretRange)) { return true; } - return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_SIEGETURRET, NextBuildPosition, STRUCTURE_PURPOSE_SIEGE); } if (!UTIL_IsStructureElectrified(ExistingTF.edict)) @@ -2013,14 +2111,29 @@ bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefini { Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); - if (vIsZero(BuildLocation)) + if (!vIsZero(BuildLocation)) { - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); + + if (bSuccess) { return true; } } + BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + if (!vIsZero(BuildLocation)) { - return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); + + if (bSuccess) { return true; } + } + + BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), HiveToSecure->FloorLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + + if (!vIsZero(BuildLocation)) + { + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); + + if (bSuccess) { return true; } } return false; @@ -2036,16 +2149,34 @@ bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefini if (!ExistingTF.IsValid()) { + // First, try and put the TF near any existing phasegate (if it exists) Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(3.0f)); - if (vIsZero(BuildLocation)) - { - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); - } - if (!vIsZero(BuildLocation)) { - return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); + + if (bSuccess) { return true; } + } + + // That failed, now try expanding the radius a bit and ignoring reachability + BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), OutpostLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + + if (!vIsZero(BuildLocation)) + { + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); + + if (bSuccess) { return true; } + } + + // That failed too, try putting it anywhere near the hive location + BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), HiveToSecure->FloorLocation, UTIL_MetresToGoldSrcUnits(5.0f)); + + if (!vIsZero(BuildLocation)) + { + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); + + if (bSuccess) { return true; } } return false; @@ -2059,11 +2190,22 @@ bool AICOMM_PerformNextSecureHiveAction(AvHAIPlayer* pBot, const AvHAIHiveDefini if (NumTurrets < 5) { - Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ExistingTF.Location, UTIL_MetresToGoldSrcUnits(3.0f)); + Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), ExistingTF.Location, (BALANCE_VAR(kCommandStationBuildDistance) * 0.8f)); if (!vIsZero(BuildLocation)) { - return AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRET, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRET, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); + + if (bSuccess) { return true; } + } + + BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), ExistingTF.Location, (BALANCE_VAR(kCommandStationBuildDistance) * 0.8f)); + + if (!vIsZero(BuildLocation)) + { + bool bSuccess = AICOMM_DeployStructure(pBot, STRUCTURE_MARINE_TURRET, BuildLocation, STRUCTURE_PURPOSE_FORTIFY); + + if (bSuccess) { return true; } } return false; diff --git a/main/source/mod/AIPlayers/AvHAIConfig.cpp b/main/source/mod/AIPlayers/AvHAIConfig.cpp index 5f170ff2..35cf889a 100644 --- a/main/source/mod/AIPlayers/AvHAIConfig.cpp +++ b/main/source/mod/AIPlayers/AvHAIConfig.cpp @@ -147,7 +147,7 @@ void CONFIG_ParseConfigFile() BotSkillLevels[3].alien_bot_view_speed = 2.0f; - string BotConfigFile = string(getModDirectory()) + "/nsbots.cfg"; + string BotConfigFile = string(getModDirectory()) + "/nsbots.ini"; const char* filename = BotConfigFile.c_str(); @@ -429,9 +429,117 @@ void CONFIG_ParseConfigFile() } } } + else + { + ALERT(at_console, "nsbots.ini was not found in the NS mod folder. You can regenerate it with the console command 'sv_regenbotini'"); + } } BotFillTiming CONFIG_GetBotFillTiming() { return CurrentBotFillTiming; +} + +void CONFIG_RegenerateIniFile() +{ + string BotConfigFile = string(getModDirectory()) + "/nsbots.ini"; + + const char* filename = BotConfigFile.c_str(); + + FILE* NewConfigFile = fopen(filename, "w+"); + + if (!NewConfigFile) + { + ALERT(at_console, "Unable to write to %s, please ensure the user has privileges\n", filename); + return; + } + + fprintf(NewConfigFile, "### General bot settings ###\n\n"); + + fprintf(NewConfigFile, "# What prefix to put in front of a bot's name (can leave blank)\n"); + fprintf(NewConfigFile, "prefix=[BOT]\n\n"); + + fprintf(NewConfigFile, "# When should the server start adding bots? Note: bots will always be added after round start regardless\n"); + fprintf(NewConfigFile, "# 0 = On map load (after 5 second grace period)\n"); + fprintf(NewConfigFile, "# 1 = When all humans have joined a team (i.e. no more humans left in ready room)\n"); + fprintf(NewConfigFile, "# 2 = When the round has started (after countdown)\n"); + fprintf(NewConfigFile, "BotFillTiming = 1\n\n\n"); + + + fprintf(NewConfigFile, "### Skill Settings ###\n\n"); + + fprintf(NewConfigFile, "# Bot skill settings. You can define as many settings as you like and reference them by name\n"); + fprintf(NewConfigFile, "# Format is BotSkillName = name, followed by one of the following:\n"); + fprintf(NewConfigFile, "# ReactionTime = How quickly in seconds the bot will react to sighting enemies\n"); + fprintf(NewConfigFile, "# AimSkill = How accurately the bot can lock sights on you after seeing you (0.0 - 1.0)\n"); + fprintf(NewConfigFile, "# MovementTracking = How accurately the bot can follow a moving target (0.0 - 1.0)\n"); + fprintf(NewConfigFile, "# ViewSpeed = How fast the bot can swivel its view (0.1 - 2.0)\n"); + fprintf(NewConfigFile, "# Set the difficulty using the 'mp_botskill' cvar (0 - 3)\n\n"); + + fprintf(NewConfigFile, "BotSkillLevel=0\n"); + fprintf(NewConfigFile, "MarineReactionTime=0.5\n"); + fprintf(NewConfigFile, "MarineAimSkill=0.1\n"); + fprintf(NewConfigFile, "MarineMovementTracking=0.1\n"); + fprintf(NewConfigFile, "MarineViewSpeed=0.5\n"); + fprintf(NewConfigFile, "AlienReactionTime=0.5\n"); + fprintf(NewConfigFile, "AlienAimSkill=0.2\n"); + fprintf(NewConfigFile, "AlienMovementTracking=0.2\n"); + fprintf(NewConfigFile, "AlienViewSpeed=0.75\n\n"); + + fprintf(NewConfigFile, "BotSkillLevel=1\n"); + fprintf(NewConfigFile, "MarineReactionTime=0.2\n"); + fprintf(NewConfigFile, "MarineAimSkill=0.5\n"); + fprintf(NewConfigFile, "MarineMovementTracking=0.4\n"); + fprintf(NewConfigFile, "MarineViewSpeed=1.0\n"); + fprintf(NewConfigFile, "AlienReactionTime=0.2\n"); + fprintf(NewConfigFile, "AlienAimSkill=0.5\n"); + fprintf(NewConfigFile, "AlienMovementTracking=0.5\n"); + fprintf(NewConfigFile, "AlienViewSpeed=1.3\n\n"); + + fprintf(NewConfigFile, "BotSkillLevel=2\n"); + fprintf(NewConfigFile, "MarineReactionTime=0.2\n"); + fprintf(NewConfigFile, "MarineAimSkill=0.6\n"); + fprintf(NewConfigFile, "MarineMovementTracking=0.6\n"); + fprintf(NewConfigFile, "MarineViewSpeed=1.5\n"); + fprintf(NewConfigFile, "AlienReactionTime=0.2\n"); + fprintf(NewConfigFile, "AlienAimSkill=0.8\n"); + fprintf(NewConfigFile, "AlienMovementTracking=0.8\n"); + fprintf(NewConfigFile, "AlienViewSpeed=1.5\n\n"); + + fprintf(NewConfigFile, "BotSkillLevel=3\n"); + fprintf(NewConfigFile, "MarineReactionTime=0.1\n"); + fprintf(NewConfigFile, "MarineAimSkill=1.0\n"); + fprintf(NewConfigFile, "MarineMovementTracking=1.0\n"); + fprintf(NewConfigFile, "MarineViewSpeed=2.0\n"); + fprintf(NewConfigFile, "AlienReactionTime=0.1\n"); + fprintf(NewConfigFile, "AlienAimSkill=1.0\n"); + fprintf(NewConfigFile, "AlienMovementTracking=1.0\n"); + fprintf(NewConfigFile, "AlienViewSpeed=2.0\n\n"); + + fprintf(NewConfigFile, "# Desired team sizes. Only used if bot fill mode is 'fillteams'\n"); + fprintf(NewConfigFile, "# Format is TeamSize=mapname:nummarines/numaliens\n"); + fprintf(NewConfigFile, "# 'default' will be used if playing a map not listed below\n"); + fprintf(NewConfigFile, "TeamSize=default:7/7\n"); + fprintf(NewConfigFile, "TeamSize=ns_machina:8/8\n"); + fprintf(NewConfigFile, "TeamSize=ns_ragnarok:8/8\n"); + fprintf(NewConfigFile, "TeamSize=co_faceoff:4/4\n"); + fprintf(NewConfigFile, "TeamSize=co_core:4/4\n"); + fprintf(NewConfigFile, "TeamSize=co_pulse:6/6\n"); + fprintf(NewConfigFile, "TeamSize=co_ulysses:6/6\n"); + fprintf(NewConfigFile, "TeamSize=co_niveus:5/5\n"); + fprintf(NewConfigFile, "TeamSize=co_kestrel:5/5\n\n\n"); + + + fprintf(NewConfigFile, "### Alien Settings ###\n\n"); + + fprintf(NewConfigFile, "# Preferred chamber sequence. Valid entries are 'defense', 'movement' and 'sensory'. Separate sequence with forward slash\n"); + fprintf(NewConfigFile, "# You can also use ? for random, so if you want movement always first but then defense and sensory at random, use\n"); + fprintf(NewConfigFile, "# ChamberSequence:movement/?/?\n"); + fprintf(NewConfigFile, "# Or if you want sensory always last, but movement and defence random, use\n"); + fprintf(NewConfigFile, "# ChamberSequence=?/?/sensory\n"); + fprintf(NewConfigFile, "ChamberSequence=defense/movement/sensory\n"); + + fflush(NewConfigFile); + fclose(NewConfigFile); + } \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAIConfig.h b/main/source/mod/AIPlayers/AvHAIConfig.h index 9548f8b2..73afa3d7 100644 --- a/main/source/mod/AIPlayers/AvHAIConfig.h +++ b/main/source/mod/AIPlayers/AvHAIConfig.h @@ -58,6 +58,6 @@ bot_skill CONFIG_GetBotSkillLevel(); BotFillTiming CONFIG_GetBotFillTiming(); - +void CONFIG_RegenerateIniFile(); #endif \ No newline at end of file diff --git a/main/source/mod/AIPlayers/AvHAIConstants.h b/main/source/mod/AIPlayers/AvHAIConstants.h index 35f58ab4..07802684 100644 --- a/main/source/mod/AIPlayers/AvHAIConstants.h +++ b/main/source/mod/AIPlayers/AvHAIConstants.h @@ -352,6 +352,7 @@ typedef struct _AVH_AI_BUILDABLE_STRUCTURE bool bReachabilityMarkedDirty = false; // If true, reachability flags will be recalculated for this structure bool IsValid() { return !FNullEnt(edict) && !edict->free && !(edict->v.flags & EF_NODRAW) && edict->v.deadflag == DEAD_NO; } + bool IsCompleted() { return (StructureStatusFlags & STRUCTURE_STATUS_COMPLETED); } } AvHAIBuildableStructure; diff --git a/main/source/mod/AIPlayers/AvHAINavigation.cpp b/main/source/mod/AIPlayers/AvHAINavigation.cpp index e554259e..648167ab 100644 --- a/main/source/mod/AIPlayers/AvHAINavigation.cpp +++ b/main/source/mod/AIPlayers/AvHAINavigation.cpp @@ -1706,7 +1706,7 @@ dtStatus FindFlightPathToPoint(const nav_profile &NavProfile, Vector FromLocatio if (CurrFlags == SAMPLE_POLYFLAGS_JUMP || CurrFlags == SAMPLE_POLYFLAGS_WALLCLIMB || CurrFlags == SAMPLE_POLYFLAGS_FLY) { - float MaxHeight = (CurrFlags == SAMPLE_POLYFLAGS_JUMP) ? fmaxf(PrevPoint.z, NextPathPoint.z) + 60.0f : UTIL_FindZHeightForWallClimb(path.back().Location, NextPathPoint, head_hull); + float MaxHeight = (CurrFlags == SAMPLE_POLYFLAGS_JUMP) ? fmaxf(PrevPoint.z, NextPathPoint.z) + 60.0f : UTIL_FindZHeightForWallClimb(PrevPoint, NextPathPoint, head_hull); NextPathNode.requiredZ = MaxHeight; NextPathNode.Location = PrevPoint; @@ -1752,7 +1752,7 @@ dtStatus FindFlightPathToPoint(const nav_profile &NavProfile, Vector FromLocatio } bot_path_node FinalInitialPathNode; - FinalInitialPathNode.FromLocation = path.back().Location; + FinalInitialPathNode.FromLocation = (path.size() > 0) ? path.back().Location : FromLocation; FinalInitialPathNode.Location = ToLocation; FinalInitialPathNode.area = SAMPLE_POLYAREA_GROUND; FinalInitialPathNode.flag = SAMPLE_POLYFLAGS_WALLCLIMB; @@ -5920,7 +5920,7 @@ bool MoveTo(AvHAIPlayer* pBot, const Vector Destination, const BotMoveStyle Move pBot->BotNavInfo.NextForceRecalc = 0.0f; pBot->BotNavInfo.bNavProfileChanged = false; - if (dtStatusSucceed(PathFindingStatus)) + if (dtStatusSucceed(PathFindingStatus) && BotNavInfo->CurrentPath.size() > 0) { pBot->BotNavInfo.StuckInfo.bPathFollowFailed = false; ClearBotStuckMovement(pBot); @@ -6107,7 +6107,7 @@ Vector FindClosestNavigablePointToDestination(const nav_profile& NavProfile, con dtStatus PathFindingResult = FindPathClosestToPoint(NavProfile, FromLocation, ToLocation, Path, MaxAcceptableDistance); - if (dtStatusSucceed(PathFindingResult)) + if (dtStatusSucceed(PathFindingResult) && Path.size() > 0) { return Path.back().Location; } @@ -6897,7 +6897,7 @@ bool BotRecalcPath(AvHAIPlayer* pBot, const Vector Destination) dtStatus FoundPath = FindPathClosestToPoint(pBot, pBot->BotNavInfo.MoveStyle, pBot->CurrentFloorPosition, ValidNavmeshPoint, pBot->BotNavInfo.CurrentPath, max_ai_use_reach); - if (dtStatusSucceed(FoundPath)) + if (dtStatusSucceed(FoundPath) && pBot->BotNavInfo.CurrentPath.size() > 0) { pBot->BotNavInfo.TargetDestination = Destination; pBot->BotNavInfo.ActualMoveDestination = pBot->BotNavInfo.CurrentPath.back().Location; @@ -8433,7 +8433,7 @@ void NAV_SetMoveMovementTask(AvHAIPlayer* pBot, Vector MoveLocation, DoorTrigger vector Path; dtStatus PathStatus = FindPathClosestToPoint(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, MoveLocation, Path, 200.0f); - if (dtStatusSucceed(PathStatus)) + if (dtStatusSucceed(PathStatus) && Path.size() > 0) { MoveTask->TaskLocation = Path.back().Location; } @@ -8452,7 +8452,7 @@ void NAV_SetTouchMovementTask(AvHAIPlayer* pBot, edict_t* EntityToTouch, DoorTri vector Path; dtStatus PathStatus = FindPathClosestToPoint(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, UTIL_GetCentreOfEntity(EntityToTouch), Path, 200.0f); - if (dtStatusSucceed(PathStatus)) + if (dtStatusSucceed(PathStatus) && Path.size() > 0) { MoveTask->TaskLocation = Path.back().Location; } diff --git a/main/source/mod/AIPlayers/AvHAIPlayer.cpp b/main/source/mod/AIPlayers/AvHAIPlayer.cpp index fa39c819..6d8cc862 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayer.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayer.cpp @@ -3413,8 +3413,11 @@ void AIPlayerSetMarineCapperPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task void AIPlayerSetMarineAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task) { + AvHTeamNumber BotTeam = pBot->Player->GetTeam(); + AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(BotTeam); + // Go attack sieged hive - const AvHAIHiveDefinition* ActiveSiegeHive = AITAC_GetNearestHiveUnderActiveSiege(pBot->Player->GetTeam(), pBot->Edict->v.origin); + const AvHAIHiveDefinition* ActiveSiegeHive = AITAC_GetNearestHiveUnderActiveSiege(BotTeam, pBot->Edict->v.origin); if (ActiveSiegeHive) { @@ -3434,7 +3437,9 @@ void AIPlayerSetMarineAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Tas AvHAIHiveDefinition* ThisHive = (*it); if (ThisHive->Status != HIVE_STATUS_UNBUILT) { continue; } - int NumMarinesSecuring = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), ThisHive->Location, UTIL_MetresToGoldSrcUnits(15.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); + if (AICOMM_IsHiveFullySecured(pBot, ThisHive, false)) { continue; } + + int NumMarinesSecuring = AITAC_GetNumPlayersOfTeamInArea(BotTeam, ThisHive->Location, UTIL_MetresToGoldSrcUnits(15.0f), false, pBot->Edict, AVH_USER3_COMMANDER_PLAYER); if (NumMarinesSecuring < 2) { @@ -3461,15 +3466,39 @@ void AIPlayerSetMarineAssaultPrimaryTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Tas // Go to a good siege location if phase gates available - if (AITAC_PhaseGatesAvailable(pBot->Player->GetTeam())) + if (AITAC_PhaseGatesAvailable(BotTeam)) { - const AvHAIHiveDefinition* ActiveHive = AITAC_GetActiveHiveNearestLocation(AIMGR_GetEnemyTeam(pBot->Player->GetTeam()), pBot->Edict->v.origin); + const AvHAIHiveDefinition* ActiveHive = AITAC_GetActiveHiveNearestLocation(AIMGR_GetEnemyTeam(BotTeam), pBot->Edict->v.origin); if (ActiveHive) { - if (Task->TaskType != TASK_MOVE) + DeployableSearchFilter EnemyDefences; + EnemyDefences.DeployableTeam = EnemyTeam; + EnemyDefences.DeployableTypes = STRUCTURE_ALIEN_OFFENCECHAMBER; + EnemyDefences.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + EnemyDefences.MaxSearchRadius = UTIL_MetresToGoldSrcUnits(15.0f); + + if (!AITAC_DeployableExistsAtLocation(ActiveHive->FloorLocation, &EnemyDefences) && AITAC_GetNumPlayersOnTeamWithLOS(EnemyTeam, ActiveHive->Location, UTIL_MetresToGoldSrcUnits(10.0f), nullptr) < 3) { - AITASK_SetMoveTask(pBot, Task, UTIL_GetRandomPointOnNavmeshInDonut(pBot->BotNavInfo.NavProfile, ActiveHive->FloorLocation, UTIL_MetresToGoldSrcUnits(10.0f), UTIL_MetresToGoldSrcUnits(20.0f)), false); + AITASK_SetAttackTask(pBot, Task, ActiveHive->HiveEdict, false); + return; + } + + if (Task->TaskType != TASK_GUARD || vDist2DSq(Task->TaskLocation, ActiveHive->Location) > sqrf(UTIL_MetresToGoldSrcUnits(20.0f))) + { + Vector GuardLocation = UTIL_GetRandomPointOnNavmeshInDonut(pBot->BotNavInfo.NavProfile, ActiveHive->FloorLocation, UTIL_MetresToGoldSrcUnits(15.0f), UTIL_MetresToGoldSrcUnits(25.0f)); + + if (!vIsZero(GuardLocation)) + { + + Task->TaskType = TASK_GUARD; + Task->TaskLength = 60.0f; + Task->TaskLocation = GuardLocation; + Task->bTaskIsUrgent = false; + Task->TaskStartedTime = 0.0f; + return; + } + } return; diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp index 226e7dcf..a3de69a5 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.cpp @@ -413,24 +413,12 @@ void AIMGR_AddAIPlayerToTeam(int Team) int NewBotIndex = -1; edict_t* BotEnt = nullptr; - // If game has ended, don't allow new bots to be added - if (GetGameRules()->GetVictoryTeam() != TEAM_IND) + // If bots aren't enabled or the game has ended, don't allow new bots to be added + if (!AIMGR_IsBotEnabled() || GetGameRules()->GetVictoryTeam() != TEAM_IND) { return; } - if (!NavmeshLoaded()) - { - CONFIG_ParseConfigFile(); - - const char* theCStrLevelName = STRING(gpGlobals->mapname); - - if (!loadNavigationData(theCStrLevelName)) - { - return; - } - } - if (ActiveAIPlayers.size() >= gpGlobals->maxClients) { ALERT(at_console, "Bot limit reached, cannot add more\n"); @@ -594,7 +582,6 @@ void AIMGR_UpdateAIPlayers() static int CurrentBotSkill = 1; static int UpdateIndex = 0; - static int FrameSpread = 3; CurrTime = gpGlobals->time; @@ -614,8 +601,6 @@ void AIMGR_UpdateAIPlayers() CurrentBotSkill = cvarBotSkill; } - bool bHasCommander = false; - if (bHasRoundStarted) { AvHTeamNumber TeamANumber = GetGameRules()->GetTeamANumber(); @@ -640,6 +625,15 @@ void AIMGR_UpdateAIPlayers() } } } + + int NumCommanders = AIMGR_GetNumAICommanders(); + int NumRegularBots = AIMGR_GetNumAIPlayers() - NumCommanders; + + int NumBotsThinkThisFrame = 0; + + int BotsPerFrame = ceil(BOT_THINK_RATE_HZ * NumRegularBots * FrameDelta); + + int BotIndex = 0; for (auto BotIt = ActiveAIPlayers.begin(); BotIt != ActiveAIPlayers.end();) { @@ -652,45 +646,35 @@ void AIMGR_UpdateAIPlayers() AvHAIPlayer* bot = &(*BotIt); - if (IsPlayerCommander(bot->Edict)) - { - bHasCommander = true; - } - if (bSkillChanged) { const bot_skill NewSkillSettings = CONFIG_GetBotSkillLevel(); memcpy(&bot->BotSkillSettings, &NewSkillSettings, sizeof(bot_skill)); } - int BotIndex = distance(ActiveAIPlayers.begin(), BotIt); - BotUpdateViewRotation(bot, FrameDelta); if (bHasRoundStarted) { if (IsPlayerCommander(bot->Edict)) { - if (UpdateIndex == FrameSpread) + if (UpdateIndex == -1) { AIPlayerThink(bot); } } else { - if (UpdateIndex != FrameSpread) + if (UpdateIndex > -1 && BotIndex >= UpdateIndex && NumBotsThinkThisFrame < BotsPerFrame) { - int BotModulo = BotIndex % FrameSpread; - - if (BotModulo == UpdateIndex) - { - AIPlayerThink(bot); - } + AIPlayerThink(bot); + NumBotsThinkThisFrame++; } + BotIndex++; } } - if (IS_DEDICATED_SERVER() || (CurrTime - bot->LastServerUpdateTime) >= BOT_MIN_FRAME_TIME) + if (IS_DEDICATED_SERVER() || (CurrTime - bot->LastServerUpdateTime) >= BOT_SERVER_UPDATE_RATE) { UpdateBotChat(bot); @@ -707,11 +691,25 @@ void AIMGR_UpdateAIPlayers() BotIt++; } - UpdateIndex++; - - if (UpdateIndex > FrameSpread || (!bHasCommander && UpdateIndex == FrameSpread)) + if (UpdateIndex < 0) + { + UpdateIndex = 0; + } + else { - UpdateIndex = 0; + UpdateIndex += NumBotsThinkThisFrame; + } + + if (UpdateIndex >= NumRegularBots) + { + if (NumCommanders > 0) + { + UpdateIndex = -1; + } + else + { + UpdateIndex = 0; + } } PrevTime = CurrTime; @@ -723,6 +721,21 @@ int AIMGR_GetNumAIPlayers() return ActiveAIPlayers.size(); } +int AIMGR_GetNumAICommanders() +{ + int Result = 0; + + for (auto it = ActiveAIPlayers.begin(); it != ActiveAIPlayers.end(); it++) + { + if (it->Player->GetUser3() == AVH_USER3_COMMANDER_PLAYER) + { + Result++; + } + } + + return Result; +} + AvHTeamNumber AIMGR_GetTeamANumber() { return GetGameRules()->GetTeamANumber(); @@ -1001,8 +1014,6 @@ void AIMGR_NewMap() AITAC_ClearMapAIData(true); - CONFIG_ParseConfigFile(); - AIMGR_BotPrecache(); bHasRoundStarted = false; @@ -1030,6 +1041,8 @@ void AIMGR_LoadNavigationData() // Don't reload the nav mesh if it's already loaded if (NavmeshLoaded()) { return; } + CONFIG_ParseConfigFile(); + const char* theCStrLevelName = STRING(gpGlobals->mapname); if (!loadNavigationData(theCStrLevelName)) @@ -1189,6 +1202,11 @@ void AIMGR_UpdateAIMapData() } } +void AIMGR_RegenBotIni() +{ + CONFIG_RegenerateIniFile(); +} + void AIMGR_BotPrecache() { m_spriteTexture = PRECACHE_MODEL("sprites/zbeam6.spr"); diff --git a/main/source/mod/AIPlayers/AvHAIPlayerManager.h b/main/source/mod/AIPlayers/AvHAIPlayerManager.h index 94a17c99..b1321dc5 100644 --- a/main/source/mod/AIPlayers/AvHAIPlayerManager.h +++ b/main/source/mod/AIPlayers/AvHAIPlayerManager.h @@ -4,8 +4,10 @@ #include "../AvHConstants.h" #include "AvHAIPlayer.h" -// Max rate bot can run its logic, default is 1/60th second. WARNING: Increasing the rate past 100hz causes bots to move and turn slowly due to GoldSrc limits! -static const double BOT_MIN_FRAME_TIME = (1.0 / 100.0); +// The rate at which the bot will call RunPlayerMove in, default is 100hz. WARNING: Increasing the rate past 100hz causes bots to move and turn slowly due to GoldSrc limits! +static const double BOT_SERVER_UPDATE_RATE = (1.0 / 100.0); +// The rate in hz (times per second) at which the bot will call AIPlayerThink, default is 10 times per second. +static const int BOT_THINK_RATE_HZ = 10; // Once the first human player has joined the game, how long to wait before adding bots static const float AI_GRACE_PERIOD = 5.0f; // Max time to wait before spawning players if none connect (e.g. empty dedicated server) @@ -43,9 +45,13 @@ vector AIMGR_GetAllPlayersOnTeam(AvHTeamNumber Team); int AIMGR_GetNumPlayersOnTeam(AvHTeamNumber Team); // How many AI players are in the game (does NOT include third-party bots like RCBot/Whichbot) int AIMGR_GetNumAIPlayers(); +// How many bot commanders we have (across both teams) +int AIMGR_GetNumAICommanders(); // Returns true if an AI player is on the requested team (does NOT include third-party bots like RCBot/Whichbot) int AIMGR_AIPlayerExistsOnTeam(AvHTeamNumber Team); +void AIMGR_RegenBotIni(); + void AIMGR_UpdateAIMapData(); bool AIMGR_ShouldStartPlayerBalancing(); diff --git a/main/source/mod/AIPlayers/AvHAITactical.cpp b/main/source/mod/AIPlayers/AvHAITactical.cpp index 07ab4d7b..72d23959 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.cpp +++ b/main/source/mod/AIPlayers/AvHAITactical.cpp @@ -4188,7 +4188,7 @@ vector AITAC_GetAllPlayersOnTeamWithLOS(AvHTeamNumber Team, const Ve return Results; } -bool AITAC_GetNumPlayersOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer) +int AITAC_GetNumPlayersOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer) { int Result = 0; diff --git a/main/source/mod/AIPlayers/AvHAITactical.h b/main/source/mod/AIPlayers/AvHAITactical.h index 673a42ec..066347b0 100644 --- a/main/source/mod/AIPlayers/AvHAITactical.h +++ b/main/source/mod/AIPlayers/AvHAITactical.h @@ -86,7 +86,7 @@ AvHMessageID UTIL_ItemTypeToImpulseCommand(const AvHAIDeployableItemType ItemTyp 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); -bool AITAC_GetNumPlayersOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer); +int AITAC_GetNumPlayersOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer); vector AITAC_GetAllPlayersOnTeamWithLOS(AvHTeamNumber Team, const Vector& Location, float SearchRadius, edict_t* IgnorePlayer); bool AITAC_ShouldBotBeCautious(AvHAIPlayer* pBot); diff --git a/main/source/mod/AIPlayers/AvHAITask.cpp b/main/source/mod/AIPlayers/AvHAITask.cpp index ed95ecf8..7a9c6b3c 100644 --- a/main/source/mod/AIPlayers/AvHAITask.cpp +++ b/main/source/mod/AIPlayers/AvHAITask.cpp @@ -915,7 +915,7 @@ bool AITASK_IsMarineSecureHiveTaskStillValid(AvHAIPlayer* pBot, AvHAIPlayerTask* { AvHAIBuildableStructure Structure = (*it); - if (Structure.StructureType == STRUCTURE_MARINE_TURRETFACTORY) + if (Structure.StructureType == STRUCTURE_MARINE_PHASEGATE) { bHasPhaseGate = true; } @@ -2806,7 +2806,7 @@ void AITASK_GenerateGuardWatchPoints(AvHAIPlayer* pBot, const Vector& GuardLocat dtStatus SearchResult = FindPathClosestToPoint(NavProfile, ThisHive->FloorLocation, GuardLocation, path, 500.0f); - if (dtStatusSucceed(SearchResult)) + if (dtStatusSucceed(SearchResult) && path.size() > 0) { Vector FinalApproachDir = UTIL_GetVectorNormal2D(path.back().Location - prev(prev(path.end()))->Location); Vector ProspectiveNewGuardLoc = GuardLocation - (FinalApproachDir * 300.0f); @@ -2822,7 +2822,7 @@ void AITASK_GenerateGuardWatchPoints(AvHAIPlayer* pBot, const Vector& GuardLocat dtStatus SearchResult = FindPathClosestToPoint(NavProfile, AITAC_GetTeamStartingLocation(EnemyTeam), GuardLocation, path, 500.0f); - if (dtStatusSucceed(SearchResult)) + if (dtStatusSucceed(SearchResult) && path.size() > 0) { Vector FinalApproachDir = UTIL_GetVectorNormal2D(path.back().Location - prev(prev(path.end()))->Location); Vector ProspectiveNewGuardLoc = GuardLocation - (FinalApproachDir * 300.0f); @@ -2839,7 +2839,7 @@ void AITASK_GenerateGuardWatchPoints(AvHAIPlayer* pBot, const Vector& GuardLocat { dtStatus SearchResult = FindPathClosestToPoint(NavProfile, AITAC_GetTeamStartingLocation(pBot->Player->GetTeam()), GuardLocation, path, 500.0f); - if (dtStatusSucceed(SearchResult)) + if (dtStatusSucceed(SearchResult) && path.size() > 0) { Vector FinalApproachDir = UTIL_GetVectorNormal2D(path.back().Location - prev(prev(path.end()))->Location); Vector ProspectiveNewGuardLoc = GuardLocation - (FinalApproachDir * 300.0f); @@ -3264,19 +3264,20 @@ void AITASK_SetBuildTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, edict_t* Stru if (FNullEnt(StructureToBuild) || UTIL_StructureIsFullyBuilt(StructureToBuild)) { return; } - if (Task->TaskType == TASK_BUILD && Task->TaskTarget == StructureToBuild) { return; } + if (Task->TaskType == TASK_BUILD && Task->TaskTarget == StructureToBuild) + { + Task->bTaskIsUrgent = bIsUrgent; + return; + } // Get as close as possible to desired location - Vector BuildLocation = FindClosestNavigablePointToDestination(pBot->BotNavInfo.NavProfile, pBot->CurrentFloorPosition, UTIL_GetEntityGroundLocation(StructureToBuild), 80.0f); + Vector BuildLocation = UTIL_ProjectPointToNavmesh(StructureToBuild->v.origin); - if (BuildLocation != g_vecZero) - { - Task->TaskType = TASK_BUILD; - Task->TaskTarget = StructureToBuild; - Task->TaskLocation = BuildLocation; - Task->bTaskIsUrgent = bIsUrgent; - Task->StructureType = GetStructureTypeFromEdict(StructureToBuild); - } + Task->TaskType = TASK_BUILD; + Task->TaskTarget = StructureToBuild; + Task->TaskLocation = (!vIsZero(BuildLocation)) ? BuildLocation : UTIL_GetFloorUnderEntity(StructureToBuild); + Task->bTaskIsUrgent = bIsUrgent; + Task->StructureType = GetStructureTypeFromEdict(StructureToBuild); } void AITASK_SetCapResNodeTask(AvHAIPlayer* pBot, AvHAIPlayerTask* Task, const AvHAIResourceNode* NodeRef, const bool bIsUrgent) diff --git a/main/source/mod/AvHGamerules.cpp b/main/source/mod/AvHGamerules.cpp index 1cc290fa..e13870b7 100644 --- a/main/source/mod/AvHGamerules.cpp +++ b/main/source/mod/AvHGamerules.cpp @@ -416,6 +416,11 @@ AvHGamerules::AvHGamerules() : mTeamA(TEAM_ONE), mTeamB(TEAM_TWO) } }); + REGISTER_SERVER_FUNCTION("sv_regenbotini", []() + { + AIMGR_RegenBotIni(); + }); + g_VoiceGameMgr.Init(&gVoiceHelper, gpGlobals->maxClients); #ifdef DEBUG