From 081180b0a1978f1200ef7ca1c899157f336de809 Mon Sep 17 00:00:00 2001 From: Nicole Dingens Date: Thu, 21 Aug 2025 19:24:46 +0200 Subject: [PATCH] Only the files changed for the Marine Build Order PR --- README.md | 2 + main/marine_build_orders.txt | 185 ++++++ main/source/mod/AvHAICommander.cpp | 613 ++++++++---------- .../mod/AvHAIMarineBuildOrderManager.cpp | 393 +++++++++++ .../source/mod/AvHAIMarineBuildOrderManager.h | 76 +++ main/source/mod/AvHAIPlayer.cpp | 1 - main/source/mod/AvHAIPlayerManager.cpp | 9 + 7 files changed, 919 insertions(+), 360 deletions(-) create mode 100644 main/marine_build_orders.txt create mode 100644 main/source/mod/AvHAIMarineBuildOrderManager.cpp create mode 100644 main/source/mod/AvHAIMarineBuildOrderManager.h diff --git a/README.md b/README.md index 3a55c991..db8c2477 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,8 @@ bot_drawtempobstacles Will draw nearby temporary obstacles affecting the nav The bot system uses a modified version of the Detour library from [recastnavigation](https://github.com/recastnavigation/recastnavigation). +Evobot now supports configurable build orders for the marine commander. The build orders are defined in the `marine_build_orders.txt` file. + Other bot plugins: * [RCbot](http://rcbot.bots-united.com/) * [Whichbot](https://whichbot.sourceforge.net/) diff --git a/main/marine_build_orders.txt b/main/marine_build_orders.txt new file mode 100644 index 00000000..c178c27e --- /dev/null +++ b/main/marine_build_orders.txt @@ -0,0 +1,185 @@ +# Marine Build orders + +# The Bot Commander tries to build the main base according to the listed order. +# At the start of a map a build order is selected randomly accounting for the weights. +# A higher weight means the build order is more likely to be chosen. +# The weights don't have to sum up to 1. E.g. "6" would be a valid weight. +# "ResourceTower" means that the Bot Commander prioritizes getting Res-Towers before building anything further down the list. +# So nothing below the "ResourceTower" will be done when the Bot Commander wants to prioritize rez nodes. + +# You can modify these or create your own build orders. +# Careful! It is possible to soft-lock the Bot Commander's build order. E.g. by putting the Armslab before Armoury. + +# Possible Structure names: InfantryPortal, Armoury, TurretFactory, Turrets, ResourceTower, Armslab, Observatory, Phasegate, AdvancedArmoury, Protolab +# Possible Upgrade names: Grenades,ArmorOne, WeaponsOne, PhaseTech, MotionTracking, ArmorTwo, WeaponsTwo, Catalysts, HeavyArmor, Jetpacks, ArmorThree, WeaponsThree + +# Entry syntax: entry=["Structure"/"Upgrade"],[[structure name]/[upgrade name]],[["StructureExists"]/["UpgradeExists"]/["TimeElapsed"]],[[structure name]/[upgrade name][number]],["and"/"or"],[["StructureExists"]/["UpgradeExists"]/["TimeElapsed"]],[[structure name]/[upgrade name][number]] + +# Example: entry=structure,Armslab,StructureExists,Armoury,and,TimeElapsed,60 +# Meaning: Try to build an armslab if an armoury exists and the current game is going on for at least 60 seconds. + +# A build order does not change the Bot Commander's ability to build things it can not usually build. +# All the usual rules and requirements still apply. + + +BuildOrderStart +name=Balanced +weight=0.2 +initialIPs=1 +entry=structure,Armoury,none,none,and,none,none +entry=structure,TurretFactory,TimeElapsed,180,and,none,none +entry=structure,Turrets,none,none,and,none,none +entry=structure,ResourceTower,StructureExists,Armoury,and,none,none +entry=structure,InfantryPortal,StructureExists,TurretFactory,or,TimeElapsed,180 +entry=structure,Armslab,StructureExists,TurretFactory,or,TimeElapsed,200 +entry=structure,Observatory,StructureExists,Armslab,and,none,none +entry=structure,Phasegate,StructureExists,Observatory,and,none,none +entry=structure,AdvancedArmoury,StructureExists,Phasegate,and,none,none +entry=structure,Protolab,StructureExists,AdvancedArmoury,and,none,none +entry=upgrade,Grenades,none,none,and,none,none +entry=upgrade,ArmorOne,none,none,and,none,none +entry=upgrade,WeaponsOne,none,none,and,none,none +entry=upgrade,PhaseTech,none,none,and,none,none +entry=upgrade,MotionTracking,none,none,and,none,none +entry=upgrade,ArmorTwo,none,none,and,none,none +entry=upgrade,WeaponsTwo,none,none,and,none,none +entry=upgrade,Catalysts,none,none,and,none,none +entry=upgrade,HeavyArmor,none,none,and,none,none +entry=upgrade,Jetpacks,none,none,and,none,none +entry=upgrade,ArmorThree,none,none,and,none,none +entry=upgrade,WeaponsThree,none,none,and,none,none +BuildOrderEnd + +BuildOrderStart +name=Aggressive Resources +weight=0.2 +initialIPs=1 +entry=structure,Armoury,none,none,and,none,none +entry=structure,ResourceTower,none,none,and,none,none +entry=structure,Armslab,StructureExists,Armoury,and,TimeElapsed,60 +entry=structure,InfantryPortal,StructureExists,Armslab,or,TimeElapsed,90 +entry=structure,Observatory,UpgradeExists,ArmorOne,and,UpgradeExists,WeaponsOne +entry=structure,Phasegate,StructureExists,Observatory,and,none,none +entry=structure,AdvancedArmoury,StructureExists,Phasegate,or,TimeElapsed,600 +entry=structure,Protolab,StructureExists,AdvancedArmoury,or,TimeElapsed,660 +entry=upgrade,Grenades,TimeElapsed,90,and,none,none +entry=upgrade,ArmorOne,UpgradeExists,WeaponsOne,and,none,none +entry=upgrade,WeaponsOne,none,none,and,none,none +entry=upgrade,PhaseTech,none,none,and,none,none +entry=upgrade,MotionTracking,none,none,and,none,none +entry=upgrade,ArmorTwo,UpgradeExists,WeaponsTwo,and,none,none +entry=upgrade,WeaponsTwo,none,none,and,none,none +entry=upgrade,Catalysts,none,none,and,none,none +entry=upgrade,HeavyArmor,none,none,and,none,none +entry=upgrade,Jetpacks,none,none,and,none,none +entry=upgrade,ArmorThree,UpgradeExists,WeaponsThree,and,none,none +entry=upgrade,WeaponsThree,none,none,and,none,none +BuildOrderEnd + +BuildOrderStart +name=Fast Phasegate +weight=0.2 +initialIPs=1 +entry=structure,Armoury,none,none,and,none,none +entry=structure,Observatory,none,none,and,none,none +entry=structure,Phasegate,none,none,and,none,none +entry=structure,ResourceTower,none,none,and,none,none +entry=structure,InfantryPortal,StructureExists,Phasegate,or,TimeElapsed,100 +entry=structure,Armslab,UpgradeExists,PhaseTech,and,none,none +entry=structure,AdvancedArmoury,UpgradeExists,ArmorOne,and,UpgradeExists,WeaponsTwo +entry=structure,Protolab,UpgradeExists,ArmorTwo,and,UpgradeExists,WeaponsTwo +entry=upgrade,Grenades,UpgradeExists,PhaseTech,and,none,none +entry=upgrade,ArmorOne,UpgradeExists,Grenades,and,none,none +entry=upgrade,WeaponsOne,UpgradeExists,ArmorOne,and,none,none +entry=upgrade,PhaseTech,none,none,and,none,none +entry=upgrade,MotionTracking,UpgradeExists,PhaseTech,and,none,none +entry=upgrade,ArmorTwo,UpgradeExists,WeaponsTwo,and,none,none +entry=upgrade,WeaponsTwo,UpgradeExists,WeaponsOne,and,none,none +entry=upgrade,Catalysts,UpgradeExists,WeaponsTwo,and,none,none +entry=upgrade,HeavyArmor,UpgradeExists,WeaponsTwo,and,none,none +entry=upgrade,Jetpacks,UpgradeExists,HeavyArmor,and,none,none +entry=upgrade,ArmorThree,UpgradeExists,WeaponsThree,and,none,none +entry=upgrade,WeaponsThree,UpgradeExists,HeavyArmor,and,none,none +BuildOrderEnd + +BuildOrderStart +name=Turtle +weight=0.1 +initialIPs=1 +entry=structure,Armoury,none,none,and,none,none +entry=structure,TurretFactory,none,none,and,none,none +entry=structure,Turrets,none,none,and,none,none +entry=structure,InfantryPortal,StructureExists,TurretFactory,and,none,none +entry=structure,Observatory,StructureExists,TurretFactory,and,none,none +entry=structure,Armslab,StructureExists,Observatory,and,none,none +entry=structure,AdvancedArmoury,none,none,and,none,none +entry=structure,Protolab,StructureExists,AdvancedArmoury,and,none,none +entry=structure,ResourceTower,none,none,and,none,none +entry=structure,Phasegate,StructureExists,Protolab,and,none,none +entry=upgrade,Grenades,none,none,and,none,none +entry=upgrade,ArmorOne,none,none,and,none,none +entry=upgrade,WeaponsOne,none,none,and,none,none +entry=upgrade,PhaseTech,none,none,and,none,none +entry=upgrade,MotionTracking,none,none,and,none,none +entry=upgrade,ArmorTwo,none,none,and,none,none +entry=upgrade,WeaponsTwo,none,none,and,none,none +entry=upgrade,Catalysts,none,none,and,none,none +entry=upgrade,HeavyArmor,none,none,and,none,none +entry=upgrade,Jetpacks,UpgradeExists,HeavyArmor,and,none,none +entry=upgrade,ArmorThree,none,none,and,none,none +entry=upgrade,WeaponsThree,none,none,and,none,none +BuildOrderEnd + +BuildOrderStart +name=Shotgun Rush +weight=0.1 +initialIPs=0 +entry=structure,Armoury,none,none,and,none,none +entry=structure,ResourceTower,TimeElapsed,160,and,none,none +entry=structure,InfantryPortal,TimeElapsed,120,and,none,none +entry=structure,Armslab,TimeElapsed,200,and,none,none +entry=structure,Observatory,StructureExists,Armslab,and,none,none +entry=structure,Phasegate,StructureExists,Observatory,and,none,none +entry=structure,AdvancedArmoury,StructureExists,Phasegate,and,none,none +entry=structure,Protolab,StructureExists,AdvancedArmoury,and,none,none +entry=upgrade,Grenades,none,none,and,none,none +entry=upgrade,ArmorOne,none,none,and,none,none +entry=upgrade,WeaponsOne,none,none,and,none,none +entry=upgrade,PhaseTech,none,none,and,none,none +entry=upgrade,MotionTracking,none,none,and,none,none +entry=upgrade,ArmorTwo,none,none,and,none,none +entry=upgrade,WeaponsTwo,none,none,and,none,none +entry=upgrade,Catalysts,none,none,and,none,none +entry=upgrade,HeavyArmor,none,none,and,none,none +entry=upgrade,Jetpacks,UpgradeExists,HeavyArmor,and,none,none +entry=upgrade,ArmorThree,none,none,and,none,none +entry=upgrade,WeaponsThree,none,none,and,none,none +BuildOrderEnd + +BuildOrderStart +name=Tech Rush +weight=0.2 +initialIPs=1 +entry=structure,Armoury,none,none,and,none,none +entry=structure,Armslab,none,none,and,none,none +entry=structure,ResourceTower,none,none,and,none,none +entry=structure,Observatory,UpgradeExists,ArmorOne,or,UpgradeExists,WeaponsOne +entry=structure,InfantryPortal,StructureExists,Observatory,and,TimeElapsed,180 +entry=structure,Phasegate,StructureExists,Observatory,and,none,none +entry=structure,AdvancedArmoury,UpgradeExists,ArmorTwo,or,UpgradeExists,WeaponsTwo +entry=structure,Protolab,UpgradeExists,armortwo,and,UpgradeExists,weaponstwo +entry=structure,TurretFactory,TimeElapsed,900,or,UpgradeExists,WeaponsThree +entry=structure,Turrets,StructureExists,TurretFactory,and,none,none +entry=upgrade,Grenades,UpgradeExists,ArmorOne,or,UpgradeExists,WeaponsOne +entry=upgrade,ArmorOne,none,none,and,none,none +entry=upgrade,WeaponsOne,none,none,and,none,none +entry=upgrade,PhaseTech,UpgradeExists,ArmorOne,or,UpgradeExists,WeaponsOne +entry=upgrade,MotionTracking,UpgradeExists,ArmorOne,or,UpgradeExists,WeaponsOne +entry=upgrade,ArmorTwo,none,none,and,none,none +entry=upgrade,WeaponsTwo,none,none,and,none,none +entry=upgrade,Catalysts,UpgradeExists,ArmorTwo,or,UpgradeExists,WeaponsTwo +entry=upgrade,HeavyArmor,UpgradeExists,ArmorThree,or,UpgradeExists,WeaponsThree +entry=upgrade,Jetpacks,UpgradeExists,ArmorThree,or,UpgradeExists,WeaponsThree +entry=upgrade,ArmorThree,none,none,and,none,none +entry=upgrade,WeaponsThree,none,none,and,none,none +BuildOrderEnd \ No newline at end of file diff --git a/main/source/mod/AvHAICommander.cpp b/main/source/mod/AvHAICommander.cpp index fd993289..32645ab4 100644 --- a/main/source/mod/AvHAICommander.cpp +++ b/main/source/mod/AvHAICommander.cpp @@ -9,6 +9,7 @@ #include "AvHAIHelper.h" #include "AvHAIPlayerManager.h" #include "AvHAIConfig.h" +#include "AvHAIMarineBuildOrderManager.h" #include "AvHSharedUtil.h" #include "AvHServerUtil.h" @@ -589,6 +590,42 @@ void AICOMM_UpdatePlayerOrders(AvHAIPlayer* pBot) int NumPlayersOnTeam = AITAC_GetNumActivePlayersOnTeam(pBot->Player->GetTeam()); + // Do the Shotgun rush here + if (AIBO_BuildOrderIsShotgunRush() && AIMGR_GetMatchLength() < 180) + { + DeployableSearchFilter ArmouryFilter; + ArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); + ArmouryFilter.DeployableTeam = BotTeam; + ArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + AvHAIBuildableStructure NearestArmoury = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(BotTeam), &ArmouryFilter); + Vector CommChairLocation = AITAC_GetCommChairLocation(BotTeam); + const AvHAIHiveDefinition* HiveToAttack = AITAC_GetActiveHiveNearestLocation(EnemyTeam, CommChairLocation); + + // Order all players to stay in base until 80% of them have a shotgun or some time has passed. + int NumPlayers = AIMGR_GetNumPlayersOnTeam(BotTeam); + int NumShotguns = AITAC_GetNumWeaponsInPlay(BotTeam, WEAPON_MARINE_SHOTGUN); + + if (NumShotguns < 0.8 * NumPlayers && AIMGR_GetMatchLength() < 30) + { + Vector MoveLocation = (NearestArmoury.IsValid()) ? NearestArmoury.Location : CommChairLocation; + edict_t* GuyToBuildBase = AICOMM_GetPlayerWithoutSpecificOrderNearestLocation(pBot, HiveToAttack->Location, ORDERPURPOSE_BUILD_MAINBASE); + AICOMM_AssignNewPlayerOrder(pBot, GuyToBuildBase, MoveLocation, ORDERPURPOSE_BUILD_MAINBASE); + return; + } + // Send all players to attack the hive + if (HiveToAttack) + { + edict_t* HiveAttacker = AICOMM_GetPlayerWithoutSpecificOrderNearestLocation(pBot, CommChairLocation, ORDERPURPOSE_SIEGE_HIVE); + if (HiveAttacker && !FNullEnt(HiveAttacker)) + { + // AICOMM_AssignNewPlayerOrder(pBot, HiveAttacker, HiveToAttack->Location, ORDERPURPOSE_SIEGE_HIVE); <~~ why does this crash? + AICOMM_IssueSecureHiveOrder(pBot, HiveAttacker, HiveToAttack); + } + } + return; + } + for (auto it = pBot->Bases.begin(); it != pBot->Bases.end(); it++) { AvHAIMarineBase* ThisBase = &(*it); @@ -1435,6 +1472,35 @@ bool AICOMM_CheckForNextSupplyAction(AvHAIPlayer* pBot) { AvHTeamNumber CommanderTeam = pBot->Player->GetTeam(); + // Even morer firsterer: Are we doing a shotgun rush? If so, drop a shotgun for everyone who doesn't have one yet + if (AIBO_BuildOrderIsShotgunRush() && AIMGR_GetMatchLength() < 120) + { + // As long as we have enough resources, drop a shotgun for everyone who doesn't have one yet + if (pBot->Player->GetResources() >= BALANCE_VAR(kShotgunCost)) + { + int NumPlayers = AIMGR_GetNumPlayersOnTeam(CommanderTeam); + int NumShotguns = AITAC_GetNumWeaponsInPlay(CommanderTeam, WEAPON_MARINE_SHOTGUN); + if (NumShotguns < NumPlayers) + { + DeployableSearchFilter ArmouryFilter; + ArmouryFilter.DeployableTypes = (STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY); + ArmouryFilter.DeployableTeam = CommanderTeam; + ArmouryFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + ArmouryFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; + + AvHAIBuildableStructure NearestArmoury = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &ArmouryFilter); + + if (NearestArmoury.IsValid()) + { + Vector DeployLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(MARINE_BASE_NAV_PROFILE), NearestArmoury.Location, UTIL_MetresToGoldSrcUnits(3.0f)); + bool bSuccess = AICOMM_DeployItem(pBot, DEPLOYABLE_ITEM_SHOTGUN, DeployLocation); + + return bSuccess; + } + } + } + } + // First thing: if our base is damaged and there's nobody able to weld, drop a welder so we don't let the base die bool bBaseIsDamaged = false; @@ -1801,6 +1867,8 @@ bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot) } + // We will look at the structures we can research from first, then the ones we can build + DeployableSearchFilter StructureFilter; StructureFilter.DeployableTeam = CommanderTeam; @@ -1808,138 +1876,92 @@ bool AICOMM_CheckForNextResearchAction(AvHAIPlayer* pBot) StructureFilter.ReachabilityFlags = AI_REACHABILITY_MARINE; StructureFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; - if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_GRENADES)) + // Let's put the new logic here. We iterate over the build order just like when building the main base. + if (!AIBO_CurrentBuildOrder) { - StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMOURY | STRUCTURE_MARINE_ADVARMOURY; - StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; - - AvHAIBuildableStructure Armoury = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); - - if (Armoury.IsValid()) - { - bool bSuccess = AICOMM_ResearchTech(pBot, &Armoury, RESEARCH_GRENADES); - - if (bSuccess) { return true; } - - return pBot->Player->GetResources() < (BALANCE_VAR(kGrenadesResearchCost) * 1.5f); - } + g_engfuncs.pfnServerPrint("Trying to research tech but no current build order set.\n"); + return false; } - StructureFilter.DeployableTypes = STRUCTURE_MARINE_ARMSLAB; - StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; - - AvHAIBuildableStructure ArmsLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); - - if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_ARMOR_ONE)) + // This exact check exists twice in this file. + // Perhaps we should move it to a function in AvHAIMarineBuildOrderManager? + // Note to future me: Do that! With love your past self. + for (auto& NextBuildOrderEntry : AIBO_CurrentBuildOrder->BuildOrder) { - if (ArmsLab.IsValid()) - { - bool bSuccess = AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_ARMOR_ONE); - - if (bSuccess) { return true; } + if (NextBuildOrderEntry.BuildOrderType != BUILD_ORDER_UPGRADE) { continue; } + if (NextBuildOrderEntry.UpgradeToResearch == TECH_NULL) { continue; } - return pBot->Player->GetResources() < (BALANCE_VAR(kArmorOneResearchCost) * 1.5f); - } - } + // Check the conditions defined in the build order + bool conditionOne = NextBuildOrderEntry.BuildConditionOne == CONDITION_NONE ? true : false; + bool conditionTwo = NextBuildOrderEntry.BuildConditionTwo == CONDITION_NONE ? true : false;; + bool connective = false; - if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_WEAPONS_ONE)) - { - if (ArmsLab.IsValid()) + if (NextBuildOrderEntry.BuildConditionOne == STRUCTURE_EXISTS) { - bool bSuccess = AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_WEAPONS_ONE); - - if (bSuccess) { return true; } + DeployableSearchFilter RequiredStructuresFilter; + RequiredStructuresFilter.DeployableTeam = CommanderTeam; + RequiredStructuresFilter.DeployableTypes = NextBuildOrderEntry.StructureRequiredOne; + RequiredStructuresFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + RequiredStructuresFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; - return pBot->Player->GetResources() < (BALANCE_VAR(kWeaponsOneResearchCost) * 1.5f); + vector FoundStructures = AITAC_FindAllDeployables(ZERO_VECTOR, &RequiredStructuresFilter); + conditionOne = FoundStructures.size() == 0 ? false : true; } - } - - StructureFilter.DeployableTypes = STRUCTURE_MARINE_OBSERVATORY; - StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; - - AvHAIBuildableStructure Observatory = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); - - if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_PHASETECH)) - { - if (Observatory.IsValid()) + if (NextBuildOrderEntry.BuildConditionOne == UPGRADE_EXISTS) { - bool bSuccess = AICOMM_ResearchTech(pBot, &Observatory, RESEARCH_PHASETECH); - - if (bSuccess) { return true; } - - return pBot->Player->GetResources() < (BALANCE_VAR(kPhaseTechResearchCost) * 1.5f); + conditionOne = (NextBuildOrderEntry.UpgradeRequiredOne == TECH_NULL || !AITAC_ResearchIsComplete(CommanderTeam, NextBuildOrderEntry.UpgradeRequiredOne)) ? false : true; } - } - - if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_MOTIONTRACK)) - { - if (Observatory.IsValid()) + if (NextBuildOrderEntry.BuildConditionOne == TIME_ELAPSED) { - return AICOMM_ResearchTech(pBot, &Observatory, RESEARCH_MOTIONTRACK); + conditionOne = AIMGR_GetMatchLength() < NextBuildOrderEntry.TimeLimitInSecondsOne ? false : true; } - } - if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_ARMOR_TWO)) - { - if (ArmsLab.IsValid()) + if (NextBuildOrderEntry.BuildConditionTwo == STRUCTURE_EXISTS) { - return AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_ARMOR_TWO); - } - } + DeployableSearchFilter RequiredStructuresFilter; + RequiredStructuresFilter.DeployableTeam = CommanderTeam; + RequiredStructuresFilter.DeployableTypes = NextBuildOrderEntry.StructureRequiredTwo; + RequiredStructuresFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + RequiredStructuresFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; - if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_WEAPONS_TWO)) - { - if (ArmsLab.IsValid()) - { - return AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_WEAPONS_TWO); + vector FoundStructures = AITAC_FindAllDeployables(ZERO_VECTOR, &RequiredStructuresFilter); + conditionTwo = FoundStructures.size() == 0 ? false : true; } - } - - if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_CATALYSTS)) - { - if (ArmsLab.IsValid()) + if (NextBuildOrderEntry.BuildConditionTwo == UPGRADE_EXISTS) { - return AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_CATALYSTS); + conditionTwo = (NextBuildOrderEntry.UpgradeRequiredTwo == TECH_NULL || !AITAC_ResearchIsComplete(CommanderTeam, NextBuildOrderEntry.UpgradeRequiredTwo)) ? false : true; } - } - - StructureFilter.DeployableTypes = STRUCTURE_MARINE_PROTOTYPELAB; - StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; - - AvHAIBuildableStructure ProtoLab = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); - - if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_HEAVYARMOR)) - { - if (ProtoLab.IsValid()) + if (NextBuildOrderEntry.BuildConditionTwo == TIME_ELAPSED) { - return AICOMM_ResearchTech(pBot, &ProtoLab, RESEARCH_HEAVYARMOR); + + conditionTwo = AIMGR_GetMatchLength() < NextBuildOrderEntry.TimeLimitInSecondsTwo ? false : true; } - } - if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_JETPACKS)) - { - if (ProtoLab.IsValid()) + if (NextBuildOrderEntry.Connective == AND) { - return AICOMM_ResearchTech(pBot, &ProtoLab, RESEARCH_JETPACKS); + connective = conditionOne && conditionTwo; } - } - - if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_ARMOR_THREE)) - { - if (ArmsLab.IsValid()) + if (NextBuildOrderEntry.Connective == OR) { - return AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_ARMOR_THREE); + connective = conditionOne || conditionTwo; } - } - if (AITAC_MarineResearchIsAvailable(CommanderTeam, RESEARCH_WEAPONS_THREE)) - { - if (ArmsLab.IsValid()) + if (!connective) { continue; } + // Done checking the conditions + + if (AITAC_MarineResearchIsAvailable(CommanderTeam,AIBO_MapTechIDToMessageID(NextBuildOrderEntry.UpgradeToResearch))) { - return AICOMM_ResearchTech(pBot, &ArmsLab, RESEARCH_WEAPONS_THREE); + StructureFilter.DeployableTypes = AIBO_MapTechToRequiredStructure(NextBuildOrderEntry.UpgradeToResearch); + StructureFilter.ExcludeStatusFlags |= STRUCTURE_STATUS_RESEARCHING; + AvHAIBuildableStructure NearestStructure = AITAC_FindClosestDeployableToLocation(AITAC_GetTeamStartingLocation(CommanderTeam), &StructureFilter); + if (NearestStructure.IsValid()) + { + bool bSuccess = AICOMM_ResearchTech(pBot, &NearestStructure, AIBO_MapTechIDToMessageID(NextBuildOrderEntry.UpgradeToResearch)); + if (bSuccess) { return true; } + return pBot->Player->GetResources() < (AIBO_GetResearchCost(NextBuildOrderEntry.UpgradeToResearch) * 1.5f); + } } } - return false; } @@ -3272,6 +3294,19 @@ void AICOMM_CommanderThink(AvHAIPlayer* pBot) } } + if (!BuildMessageAnnounced) + { + BuildMessageAnnounced = true; + if (AIBO_CurrentBuildOrder) + { + std::string BuildOrderMessage = GetBuildOrderMessage(); + char BuildOrderMessageBuffer[256]; + strncpy(BuildOrderMessageBuffer, BuildOrderMessage.c_str(), sizeof(BuildOrderMessageBuffer) - 1); + BuildOrderMessageBuffer[sizeof(BuildOrderMessageBuffer) - 1] = '\0'; + BotSay(pBot, false, 0.5f, BuildOrderMessageBuffer); + } + } + // Thanks to EterniumDev (Alien) for the suggestion to have the commander jump out and build if nobody is around to help if (AICOMM_ShouldCommanderLeaveChair(pBot)) { @@ -4325,6 +4360,7 @@ void AICOMM_ReceiveChatRequest(AvHAIPlayer* Commander, edict_t* Requestor, const bool AICOMM_ShouldCommanderRelocate(AvHAIPlayer* pBot) { if (!CONFIG_IsRelocationAllowed()) { return false; } + if (!AIBO_BuildOrderIsShotgunRush()) { return false; } AvHTeamNumber Team = pBot->Player->GetTeam(); AvHTeamNumber EnemyTeam = AIMGR_GetEnemyTeam(Team); @@ -4557,7 +4593,7 @@ bool AICOMM_BuildOutMainBase(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut) if (NumIncomplete > 0) { - int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(pBot->Player->GetTeam(), BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER); + int NumBuilders = AITAC_GetNumPlayersOfTeamInArea(BotTeam, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(10.0f), false, nullptr, AVH_USER3_COMMANDER_PLAYER); // Don't spam too many structures if (NumIncomplete > NumBuilders) { return false; } @@ -4607,7 +4643,14 @@ bool AICOMM_BuildOutMainBase(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut) if (!CommChair.IsCompleted()) { return false; } - if (NumInfPortals < DesiredInfPortals) + if (!AIBO_CurrentBuildOrder) + { + g_engfuncs.pfnServerPrint("Trying to build the main base but no current build order set.\n"); + return false; + } + + // Build the initial infantry portals + if (NumInfPortals < AIBO_CurrentBuildOrder->InitialInfantryPortalCount) { if (pBot->Player->GetResources() < BALANCE_VAR(kInfantryPortalCost)) { return true; } @@ -4648,304 +4691,157 @@ bool AICOMM_BuildOutMainBase(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut) return pBot->Player->GetResources() <= (BALANCE_VAR(kInfantryPortalCost) * 1.5f); } - if (!Armoury.IsValid()) + for (auto& NextBuildOrderEntry : AIBO_CurrentBuildOrder->BuildOrder) { - if (pBot->Player->GetResources() < BALANCE_VAR(kArmoryCost)) { return true; } - Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_ARMOURY, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(20.0f)); + if (NextBuildOrderEntry.BuildOrderType != BUILD_ORDER_STRUCTURE) { continue; } + if (NextBuildOrderEntry.StructureToBuild == STRUCTURE_NONE) { continue; } - if (!vIsZero(BuildLocation)) + if (NextBuildOrderEntry.StructureToBuild == STRUCTURE_MARINE_RESTOWER) { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_ARMOURY, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } + if (AICOMM_ShouldCommanderPrioritiseNodes(pBot) && pBot->Player->GetResources() < 30) { return false; } + continue; } - int NumAttempts = 0; + AvHAIBuildableStructure Structure; + int StructureCost = 0; - while (NumAttempts < 5) + switch (NextBuildOrderEntry.StructureToBuild) { - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(5.0f)); - - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_ARMOURY, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); - - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_ARMOURY, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - - NumAttempts++; + case STRUCTURE_MARINE_COMMCHAIR: + Structure = CommChair; + StructureCost = BALANCE_VAR(kCommandStationCost); + break; + case STRUCTURE_MARINE_ARMSLAB: + Structure = ArmsLab; + StructureCost = BALANCE_VAR(kArmsLabCost); + break; + case STRUCTURE_MARINE_PROTOTYPELAB: + Structure = ProtoLab; + StructureCost = BALANCE_VAR(kPrototypeLabCost); + break; + case STRUCTURE_MARINE_PHASEGATE: + Structure = PhaseGate; + StructureCost = BALANCE_VAR(kPhaseGateCost); + break; + case STRUCTURE_MARINE_ARMOURY: + Structure = Armoury; + StructureCost = BALANCE_VAR(kArmoryCost); + break; + case STRUCTURE_MARINE_ADVARMOURY: + Structure = Armoury; + StructureCost = BALANCE_VAR(kArmoryUpgradeCost); + break; + case STRUCTURE_MARINE_TURRETFACTORY: + Structure = TurretFactory; + StructureCost = BALANCE_VAR(kTurretFactoryCost); + break; + case STRUCTURE_MARINE_OBSERVATORY: + Structure = Observatory; + StructureCost = BALANCE_VAR(kObservatoryCost); + break; + case STRUCTURE_MARINE_INFANTRYPORTAL: + if (NumInfPortals >= DesiredInfPortals) { continue; } + StructureCost = BALANCE_VAR(kInfantryPortalCost); + break; + case STRUCTURE_MARINE_TURRET: + if (NumTurrets >= 5) { continue; } + StructureCost = BALANCE_VAR(kSentryCost); + break; + default: + break; } - return pBot->Player->GetResources() <= (BALANCE_VAR(kInfantryPortalCost) * 1.5f); - } + if (NextBuildOrderEntry.StructureToBuild == STRUCTURE_MARINE_ADVARMOURY && Armoury.StructureType == STRUCTURE_MARINE_ADVARMOURY) { continue; } + if (Structure.IsValid() && NextBuildOrderEntry.StructureToBuild != STRUCTURE_MARINE_ADVARMOURY) { continue; } + if (NextBuildOrderEntry.StructureToBuild == STRUCTURE_MARINE_INFANTRYPORTAL && NumInfPortals >= DesiredInfPortals) { continue; } + if (NextBuildOrderEntry.StructureToBuild == STRUCTURE_MARINE_TURRET && (!TurretFactory.IsValid() || !TurretFactory.IsCompleted()) ) { continue; } + if (NextBuildOrderEntry.StructureToBuild == STRUCTURE_MARINE_TURRET && NumTurrets >= 5) { continue; } - if (!TurretFactory.IsValid()) - { - if (pBot->Player->GetResources() < BALANCE_VAR(kTurretFactoryCost)) { return true; } + if (pBot->Player->GetResources() < StructureCost) { return true; } - Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_TURRETFACTORY, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(20.0f)); + // Check all the conditions for this entry + bool conditionOne = NextBuildOrderEntry.BuildConditionOne == CONDITION_NONE ? true : false; + bool conditionTwo = NextBuildOrderEntry.BuildConditionTwo == CONDITION_NONE ? true : false;; + bool connective = false; - if (!vIsZero(BuildLocation)) + if (NextBuildOrderEntry.BuildConditionOne == STRUCTURE_EXISTS) { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, BaseToBuildOut); + DeployableSearchFilter RequiredStructuresFilter; + RequiredStructuresFilter.DeployableTeam = BotTeam; + RequiredStructuresFilter.DeployableTypes = NextBuildOrderEntry.StructureRequiredOne; + RequiredStructuresFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + RequiredStructuresFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; - if (bSuccess) { return true; } + vector FoundStructures = AITAC_FindAllDeployables(ZERO_VECTOR, &RequiredStructuresFilter); + conditionOne = FoundStructures.size() == 0 ? false : true; } - - if (NumTurrets > 0) + if (NextBuildOrderEntry.BuildConditionOne == UPGRADE_EXISTS) { - Vector Centroid = ZERO_VECTOR; - - for (auto tLocs = TurretLocations.begin(); tLocs != TurretLocations.end(); tLocs++) - { - Centroid = Centroid + *tLocs; - } - - Centroid = Centroid / TurretLocations.size(); - - if (!vIsZero(Centroid)) - { - Vector CentroidBuildLocation = UTIL_ProjectPointToNavmesh(Centroid, GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE)); - - if (!vIsZero(CentroidBuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_TURRETFACTORY, CentroidBuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - } + conditionOne = (NextBuildOrderEntry.UpgradeRequiredOne == TECH_NULL || !AITAC_ResearchIsComplete(BotTeam, NextBuildOrderEntry.UpgradeRequiredOne)) ? false : true; } - - int NumAttempts = 0; - - while (NumAttempts < 5) + if (NextBuildOrderEntry.BuildConditionOne == TIME_ELAPSED) { - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); - - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); - - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_TURRETFACTORY, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - - NumAttempts++; + conditionOne = AIMGR_GetMatchLength() < NextBuildOrderEntry.TimeLimitInSecondsOne ? false : true; } - return pBot->Player->GetResources() <= (BALANCE_VAR(kTurretFactoryCost) * 1.5f); - } - - if (!TurretFactory.IsCompleted()) { return false; } - - if (NumTurrets < 5) - { - if (pBot->Player->GetResources() < BALANCE_VAR(kSentryCost)) { return true; } - - Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_TURRET, TurretFactory.Location, BALANCE_VAR(kTurretFactoryBuildDistance)); - - if (!vIsZero(BuildLocation)) + if (NextBuildOrderEntry.BuildConditionTwo == STRUCTURE_EXISTS) { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_TURRET, BuildLocation, BaseToBuildOut); + DeployableSearchFilter RequiredStructuresFilter; + RequiredStructuresFilter.DeployableTeam = BotTeam; + RequiredStructuresFilter.DeployableTypes = NextBuildOrderEntry.StructureRequiredTwo; + RequiredStructuresFilter.IncludeStatusFlags = STRUCTURE_STATUS_COMPLETED; + RequiredStructuresFilter.ExcludeStatusFlags = STRUCTURE_STATUS_RECYCLING; - if (bSuccess) { return true; } + vector FoundStructures = AITAC_FindAllDeployables(ZERO_VECTOR, &RequiredStructuresFilter); + conditionTwo = FoundStructures.size() == 0 ? false : true; } - - int NumAttempts = 0; - - while (NumAttempts < 5) + if (NextBuildOrderEntry.BuildConditionTwo == UPGRADE_EXISTS) { - Vector BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), TurretFactory.Location, BALANCE_VAR(kTurretFactoryBuildDistance) * 0.8f); - - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_TURRET, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), TurretFactory.Location, BALANCE_VAR(kTurretFactoryBuildDistance) * 0.8f); - - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_TURRET, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - - NumAttempts++; + conditionTwo = (NextBuildOrderEntry.UpgradeRequiredTwo == TECH_NULL || !AITAC_ResearchIsComplete(BotTeam, NextBuildOrderEntry.UpgradeRequiredTwo)) ? false : true; } - - return pBot->Player->GetResources() <= (BALANCE_VAR(kSentryCost) * 1.5f); - } - - if (!Armoury.IsCompleted()) { return false; } - - if (AICOMM_ShouldCommanderPrioritiseNodes(pBot) && pBot->Player->GetResources() < 30) { return false; } - - if (!ArmsLab.IsValid()) - { - if (pBot->Player->GetResources() < BALANCE_VAR(kArmsLabCost)) { return true; } - - Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_ARMSLAB, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(20.0f)); - - if (!vIsZero(BuildLocation)) + if (NextBuildOrderEntry.BuildConditionTwo == TIME_ELAPSED) { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_ARMSLAB, BuildLocation, BaseToBuildOut); - if (bSuccess) { return true; } + conditionTwo = AIMGR_GetMatchLength() < NextBuildOrderEntry.TimeLimitInSecondsTwo ? false : true; } - int NumAttempts = 0; - - while (NumAttempts < 5) + if (NextBuildOrderEntry.Connective == AND) { - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); - - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_ARMSLAB, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); - - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_ARMSLAB, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - - NumAttempts++; + connective = conditionOne && conditionTwo; } - - return pBot->Player->GetResources() <= (BALANCE_VAR(kArmsLabCost) * 1.5f); - } - - if (!Observatory.IsValid()) - { - if (pBot->Player->GetResources() < BALANCE_VAR(kObservatoryCost)) { return true; } - - Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_OBSERVATORY, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(20.0f)); - - if (!vIsZero(BuildLocation)) + if (NextBuildOrderEntry.Connective == OR) { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_OBSERVATORY, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } + connective = conditionOne || conditionTwo; } - int NumAttempts = 0; + if (!connective) { continue; } + // Done checking the conditions - while (NumAttempts < 5) + // Maybe upgrade the armoury to the advanced armoury + if (NextBuildOrderEntry.StructureToBuild == STRUCTURE_MARINE_ADVARMOURY) { - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); + if (Armoury.StructureType == STRUCTURE_MARINE_ADVARMOURY) { return false; } + if (pBot->Player->GetResources() < BALANCE_VAR(kArmoryUpgradeCost)) { return true; } - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_OBSERVATORY, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); - - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_OBSERVATORY, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - - NumAttempts++; - } - - return pBot->Player->GetResources() <= (BALANCE_VAR(kObservatoryCost) * 1.5f); - } - - if (!AITAC_ResearchIsComplete(BotTeam, TECH_RESEARCH_PHASETECH)) { return false; } - - if (!PhaseGate.IsValid()) - { - if (pBot->Player->GetResources() < BALANCE_VAR(kPhaseGateCost)) { return true; } - - Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_PHASEGATE, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(20.0f)); - - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, BaseToBuildOut); + bool bSuccess = AICOMM_UpgradeStructure(pBot, &Armoury); if (bSuccess) { return true; } - } - int NumAttempts = 0; + return pBot->Player->GetResources() <= (BALANCE_VAR(kArmoryUpgradeCost) * 1.5f); + } - while (NumAttempts < 5) + if (!connective) { - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); - - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadius(GetBaseNavProfile(ONOS_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); - - if (!vIsZero(BuildLocation)) - { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_PHASEGATE, BuildLocation, BaseToBuildOut); - - if (bSuccess) { return true; } - } - - NumAttempts++; + std::string debugMessage = "We are trying to build " + std::to_string(NextBuildOrderEntry.StructureToBuild) + " even though we shouldn't."; + g_engfuncs.pfnServerPrint(debugMessage.c_str()); } - return pBot->Player->GetResources() <= (BALANCE_VAR(kPhaseGateCost) * 1.5f); - } - - if (Armoury.StructureType != STRUCTURE_MARINE_ADVARMOURY) - { - if (pBot->Player->GetResources() < BALANCE_VAR(kArmoryUpgradeCost)) { return true; } - - bool bSuccess = AICOMM_UpgradeStructure(pBot, &Armoury); - - if (bSuccess) { return true; } - - return pBot->Player->GetResources() <= (BALANCE_VAR(kArmoryUpgradeCost) * 1.5f); - } - - if (!ProtoLab.IsValid()) - { - if (pBot->Player->GetResources() < BALANCE_VAR(kPrototypeLabCost)) { return true; } - - Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(STRUCTURE_MARINE_PROTOTYPELAB, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(20.0f)); + Vector BuildLocation = AITAC_GetRandomBuildHintInLocation(NextBuildOrderEntry.StructureToBuild, BaseToBuildOut->BaseLocation, UTIL_MetresToGoldSrcUnits(20.0f)); if (!vIsZero(BuildLocation)) { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_PROTOTYPELAB, BuildLocation, BaseToBuildOut); + bool bSuccess = AICOMM_AddStructureToBase(pBot, NextBuildOrderEntry.StructureToBuild, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } @@ -4954,11 +4850,11 @@ bool AICOMM_BuildOutMainBase(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut) while (NumAttempts < 5) { - BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), Armoury.Location, UTIL_MetresToGoldSrcUnits(5.0f)); + BuildLocation = UTIL_GetRandomPointOnNavmeshInRadiusIgnoreReachability(GetBaseNavProfile(STRUCTURE_BASE_NAV_PROFILE), CommChair.Location, UTIL_MetresToGoldSrcUnits(10.0f)); if (!vIsZero(BuildLocation)) { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_PROTOTYPELAB, BuildLocation, BaseToBuildOut); + bool bSuccess = AICOMM_AddStructureToBase(pBot, NextBuildOrderEntry.StructureToBuild, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } @@ -4967,7 +4863,7 @@ bool AICOMM_BuildOutMainBase(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut) if (!vIsZero(BuildLocation)) { - bool bSuccess = AICOMM_AddStructureToBase(pBot, STRUCTURE_MARINE_PROTOTYPELAB, BuildLocation, BaseToBuildOut); + bool bSuccess = AICOMM_AddStructureToBase(pBot, NextBuildOrderEntry.StructureToBuild, BuildLocation, BaseToBuildOut); if (bSuccess) { return true; } } @@ -4975,9 +4871,8 @@ bool AICOMM_BuildOutMainBase(AvHAIPlayer* pBot, AvHAIMarineBase* BaseToBuildOut) NumAttempts++; } - return pBot->Player->GetResources() <= (BALANCE_VAR(kPrototypeLabCost) * 1.5f); + return pBot->Player->GetResources() <= (StructureCost * 1.5f); } - return false; } diff --git a/main/source/mod/AvHAIMarineBuildOrderManager.cpp b/main/source/mod/AvHAIMarineBuildOrderManager.cpp new file mode 100644 index 00000000..7d908947 --- /dev/null +++ b/main/source/mod/AvHAIMarineBuildOrderManager.cpp @@ -0,0 +1,393 @@ +#include "AvHAIMarineBuildOrderManager.h" +#include +#include + +std::vector AIBO_MarineBuildOrders; +Marine_build_order* AIBO_CurrentBuildOrder = nullptr; +bool BuildMessageAnnounced = false; + +bool bo_rng_initialized = false; +int BuildMessageAnnouncementIndex = 0; + +const std::vector BuildOrderMessageTemplates = { + "Guys today we're doing a nice {} build!", + "Today we're going to do a {} build, guys!", + "I'm feeling like trying a {} build today, my friends!", + "Let's go for a {} build today, team!", + "Why not do the famous {} build today, everyone?", + "I'm not sure about you, but I'm excited to try the {} build this time!", +}; + +int AIBO_IntRandomRange(int MinValue, int MaxValue) +{ + if (MinValue == MaxValue) { return MinValue; } + return MinValue + (rand() % (MaxValue - MinValue + 1)); +} + +float AIBO_FloatRandomRange(float MinValue, float MaxValue) +{ + if (MinValue == MaxValue) { return MinValue; } + return ((float(rand()) / float(RAND_MAX)) * (MaxValue - MinValue)) + MinValue; +} + +AvHAIDeployableStructureType AIBO_MapStringToStructure(const std::string& StructureName) +{ + if (!stricmp(StructureName.c_str(), "CommChair")) + return STRUCTURE_MARINE_COMMCHAIR; + if (!stricmp(StructureName.c_str(), "InfantryPortal")) + return STRUCTURE_MARINE_INFANTRYPORTAL; + if (!stricmp(StructureName.c_str(), "Armoury") || !stricmp(StructureName.c_str(), "Armory")) + return STRUCTURE_MARINE_ARMOURY; + if (!stricmp(StructureName.c_str(), "TurretFactory")) + return STRUCTURE_MARINE_TURRETFACTORY; + if (!stricmp(StructureName.c_str(), "Turrets")) + return STRUCTURE_MARINE_TURRET; + if (!stricmp(StructureName.c_str(), "Resourcetower") || !stricmp(StructureName.c_str(), "Restower") || !stricmp(StructureName.c_str(), "Resourcetowers") || !stricmp(StructureName.c_str(), "Restowers")) + return STRUCTURE_MARINE_RESTOWER; + if (!stricmp(StructureName.c_str(), "Armslab")) + return STRUCTURE_MARINE_ARMSLAB; + if (!stricmp(StructureName.c_str(), "Observatory")) + return STRUCTURE_MARINE_OBSERVATORY; + if (!stricmp(StructureName.c_str(), "Phasegate")) + return STRUCTURE_MARINE_PHASEGATE; + if (!stricmp(StructureName.c_str(), "AdvancedArmoury") || !stricmp(StructureName.c_str(), "AdvancedArmory")) + return STRUCTURE_MARINE_ADVARMOURY; + if (!stricmp(StructureName.c_str(), "ProtoLab")) + return STRUCTURE_MARINE_PROTOTYPELAB; + + return STRUCTURE_NONE; +} + +AvHAIDeployableStructureType AIBO_MapTechToRequiredStructure(AvHTechID TechID) +{ + switch (TechID) + { + case TECH_RESEARCH_ELECTRICAL: return STRUCTURE_MARINE_TURRETFACTORY; + case TECH_RESEARCH_ARMOR_ONE: return STRUCTURE_MARINE_ARMSLAB; + case TECH_RESEARCH_ARMOR_TWO: return STRUCTURE_MARINE_ARMSLAB; + case TECH_RESEARCH_ARMOR_THREE: return STRUCTURE_MARINE_ARMSLAB; + case TECH_RESEARCH_WEAPONS_ONE: return STRUCTURE_MARINE_ARMSLAB; + case TECH_RESEARCH_WEAPONS_TWO: return STRUCTURE_MARINE_ARMSLAB; + case TECH_RESEARCH_WEAPONS_THREE: return STRUCTURE_MARINE_ARMSLAB; + case TECH_ADVANCED_TURRET_FACTORY: return STRUCTURE_MARINE_TURRETFACTORY; + case TECH_RESEARCH_JETPACKS: return STRUCTURE_MARINE_PROTOTYPELAB; + case TECH_RESEARCH_HEAVYARMOR: return STRUCTURE_MARINE_PROTOTYPELAB; + case TECH_RESEARCH_DISTRESSBEACON: return STRUCTURE_MARINE_OBSERVATORY; + case TECH_RESEARCH_MOTIONTRACK: return STRUCTURE_MARINE_OBSERVATORY; + case TECH_RESEARCH_PHASETECH: return STRUCTURE_MARINE_OBSERVATORY; + case TECH_RESEARCH_CATALYSTS: return STRUCTURE_MARINE_ARMSLAB; + case TECH_RESEARCH_GRENADES: return STRUCTURE_MARINE_ARMOURY; + default: return STRUCTURE_NONE; // No structure required for this tech + } +} + +AvHAIBuildCondition AIBO_MapStringToBuildCondition(const std::string& ConditionName) +{ + if (!stricmp(ConditionName.c_str(), "StructureExists")) + return STRUCTURE_EXISTS; + if (!stricmp(ConditionName.c_str(), "UpgradeExists")) + return UPGRADE_EXISTS; + if (!stricmp(ConditionName.c_str(), "TimeElapsed")) + return TIME_ELAPSED; + return CONDITION_NONE; +} + +AvHAIBuildOrderType AIBO_MapStringToBuildOrderType(const std::string& BuildOrderTypeName) +{ + if (!stricmp(BuildOrderTypeName.c_str(), "Structure")) + return BUILD_ORDER_STRUCTURE; + if (!stricmp(BuildOrderTypeName.c_str(), "Upgrade")) + return BUILD_ORDER_UPGRADE; + return BUILD_ORDER_NONE; +} + +AvHTechID AIBO_MapStringToTech(const std::string& TechName) +{ + if (!stricmp(TechName.c_str(), "Electrification")) + return TECH_RESEARCH_ELECTRICAL; + if (!stricmp(TechName.c_str(), "ArmorOne")) + return TECH_RESEARCH_ARMOR_ONE; + if (!stricmp(TechName.c_str(), "ArmorTwo")) + return TECH_RESEARCH_ARMOR_TWO; + if (!stricmp(TechName.c_str(), "ArmorThree")) + return TECH_RESEARCH_ARMOR_THREE; + if (!stricmp(TechName.c_str(), "WeaponsOne")) + return TECH_RESEARCH_WEAPONS_ONE; + if (!stricmp(TechName.c_str(), "WeaponsTwo")) + return TECH_RESEARCH_WEAPONS_TWO; + if (!stricmp(TechName.c_str(), "WeaponsThree")) + return TECH_RESEARCH_WEAPONS_THREE; + if (!stricmp(TechName.c_str(), "AdvancedTurretFactory")) + return TECH_ADVANCED_TURRET_FACTORY; + if (!stricmp(TechName.c_str(), "Jetpacks")) + return TECH_RESEARCH_JETPACKS; + if (!stricmp(TechName.c_str(), "HeavyArmor")) + return TECH_RESEARCH_HEAVYARMOR; + if (!stricmp(TechName.c_str(), "DistressBeacon")) + return TECH_RESEARCH_DISTRESSBEACON; + if (!stricmp(TechName.c_str(), "HealthTech")) + return TECH_RESEARCH_HEALTH; + if (!stricmp(TechName.c_str(), "MotionTracking")) + return TECH_RESEARCH_MOTIONTRACK; + if (!stricmp(TechName.c_str(), "PhaseTech")) + return TECH_RESEARCH_PHASETECH; + if (!stricmp(TechName.c_str(), "Catalysts")) + return TECH_RESEARCH_CATALYSTS; + if (!stricmp(TechName.c_str(), "Grenades")) + return TECH_RESEARCH_GRENADES; + return TECH_NULL; +} + +AvHMessageID AIBO_MapTechIDToMessageID(AvHTechID TechID) +{ + switch (TechID) + { + case TECH_RESEARCH_ELECTRICAL: return RESEARCH_ELECTRICAL; + case TECH_RESEARCH_ARMOR_ONE: return RESEARCH_ARMOR_ONE; + case TECH_RESEARCH_ARMOR_TWO: return RESEARCH_ARMOR_TWO; + case TECH_RESEARCH_ARMOR_THREE: return RESEARCH_ARMOR_THREE; + case TECH_RESEARCH_WEAPONS_ONE: return RESEARCH_WEAPONS_ONE; + case TECH_RESEARCH_WEAPONS_TWO: return RESEARCH_WEAPONS_TWO; + case TECH_RESEARCH_WEAPONS_THREE: return RESEARCH_WEAPONS_THREE; + case TECH_ADVANCED_TURRET_FACTORY: return TURRET_FACTORY_UPGRADE; + case TECH_RESEARCH_JETPACKS: return RESEARCH_JETPACKS; + case TECH_RESEARCH_HEAVYARMOR: return RESEARCH_HEAVYARMOR; + case TECH_RESEARCH_DISTRESSBEACON: return RESEARCH_DISTRESSBEACON; + case TECH_RESEARCH_HEALTH: return RESEARCH_HEALTH; + case TECH_RESEARCH_MOTIONTRACK: return RESEARCH_MOTIONTRACK; + case TECH_RESEARCH_PHASETECH: return RESEARCH_PHASETECH; + case TECH_RESEARCH_CATALYSTS: return RESEARCH_CATALYSTS; + case TECH_RESEARCH_GRENADES: return RESEARCH_GRENADES; + default: return MESSAGE_NULL; + } +} + +int AIBO_GetResearchCost(AvHTechID TechID) +{ + switch (TechID) + { + case TECH_RESEARCH_ELECTRICAL: return (BALANCE_VAR(kElectricalUpgradeResearchCost)); + case TECH_RESEARCH_ARMOR_ONE: return (BALANCE_VAR(kArmorOneResearchCost)); + case TECH_RESEARCH_ARMOR_TWO: return (BALANCE_VAR(kArmorTwoResearchCost)); + case TECH_RESEARCH_ARMOR_THREE: return (BALANCE_VAR(kArmorThreeResearchCost)); + case TECH_RESEARCH_WEAPONS_ONE: return (BALANCE_VAR(kWeaponsOneResearchCost)); + case TECH_RESEARCH_WEAPONS_TWO: return (BALANCE_VAR(kWeaponsTwoResearchCost)); + case TECH_RESEARCH_WEAPONS_THREE: return (BALANCE_VAR(kWeaponsThreeResearchCost)); + case TECH_ADVANCED_TURRET_FACTORY: return (BALANCE_VAR(kTurretFactoryUpgradeCost)); + case TECH_RESEARCH_JETPACKS: return (BALANCE_VAR(kJetpacksResearchCost)); + case TECH_RESEARCH_HEAVYARMOR: return (BALANCE_VAR(kHeavyArmorResearchCost)); + case TECH_RESEARCH_MOTIONTRACK: return (BALANCE_VAR(kMotionTrackingResearchCost)); + case TECH_RESEARCH_PHASETECH: return (BALANCE_VAR(kPhaseTechResearchCost)); + case TECH_RESEARCH_CATALYSTS: return (BALANCE_VAR(kCatalystResearchCost)); + case TECH_RESEARCH_GRENADES: return (BALANCE_VAR(kGrenadesResearchCost)); + default: return -1; // No cost for this tech + } +} + +void AIBO_LoadHardCodedMarineBuildOrder() +{ + Marine_build_order defaultBuildOrder; + std::vector entries; + // Add each entry using push_back instead of initializer list + entries.push_back(Build_order_entry{BUILD_ORDER_STRUCTURE, STRUCTURE_MARINE_ARMOURY, TECH_NULL, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL, AND, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL}); + entries.push_back(Build_order_entry{BUILD_ORDER_STRUCTURE, STRUCTURE_MARINE_TURRETFACTORY, TECH_NULL, TIME_ELAPSED, STRUCTURE_NONE, TECH_NULL, AND, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL, 180, 0}); + entries.push_back(Build_order_entry{BUILD_ORDER_STRUCTURE, STRUCTURE_MARINE_TURRET, TECH_NULL, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL, AND, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL}); + entries.push_back(Build_order_entry{BUILD_ORDER_STRUCTURE, STRUCTURE_MARINE_RESTOWER, TECH_NULL, STRUCTURE_EXISTS, STRUCTURE_MARINE_ARMOURY, TECH_NULL, AND, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL}); + entries.push_back(Build_order_entry{BUILD_ORDER_STRUCTURE, STRUCTURE_MARINE_INFANTRYPORTAL, TECH_NULL, TIME_ELAPSED, STRUCTURE_NONE, TECH_NULL, AND, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL, 180, 0 }); + entries.push_back(Build_order_entry{BUILD_ORDER_STRUCTURE, STRUCTURE_MARINE_ARMSLAB, TECH_NULL, STRUCTURE_EXISTS, STRUCTURE_MARINE_TURRETFACTORY, TECH_NULL, OR, TIME_ELAPSED, STRUCTURE_NONE, TECH_NULL, 200, 0}); + entries.push_back(Build_order_entry{BUILD_ORDER_STRUCTURE, STRUCTURE_MARINE_OBSERVATORY, TECH_NULL, STRUCTURE_EXISTS, STRUCTURE_MARINE_ARMSLAB}); + entries.push_back(Build_order_entry{BUILD_ORDER_STRUCTURE, STRUCTURE_MARINE_PHASEGATE, TECH_NULL, STRUCTURE_EXISTS, STRUCTURE_MARINE_OBSERVATORY}); + entries.push_back(Build_order_entry{BUILD_ORDER_STRUCTURE, STRUCTURE_MARINE_ADVARMOURY, TECH_NULL, STRUCTURE_EXISTS, STRUCTURE_MARINE_PHASEGATE}); + entries.push_back(Build_order_entry{BUILD_ORDER_STRUCTURE, STRUCTURE_MARINE_PROTOTYPELAB, TECH_NULL, STRUCTURE_EXISTS ,STRUCTURE_MARINE_ADVARMOURY }); + entries.push_back(Build_order_entry{BUILD_ORDER_UPGRADE ,STRUCTURE_NONE ,TECH_RESEARCH_GRENADES ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL ,AND ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL }); + entries.push_back(Build_order_entry{BUILD_ORDER_UPGRADE ,STRUCTURE_NONE ,TECH_RESEARCH_ARMOR_ONE ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL ,AND ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL }); + entries.push_back(Build_order_entry{BUILD_ORDER_UPGRADE ,STRUCTURE_NONE ,TECH_RESEARCH_WEAPONS_ONE ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL ,AND ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL }); + entries.push_back(Build_order_entry{BUILD_ORDER_UPGRADE ,STRUCTURE_NONE ,TECH_RESEARCH_PHASETECH ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL ,AND ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL }); + entries.push_back(Build_order_entry{BUILD_ORDER_UPGRADE ,STRUCTURE_NONE ,TECH_RESEARCH_MOTIONTRACK ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL ,AND ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL }); + entries.push_back(Build_order_entry{BUILD_ORDER_UPGRADE ,STRUCTURE_NONE ,TECH_RESEARCH_ARMOR_TWO ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL ,AND ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL }); + entries.push_back(Build_order_entry{BUILD_ORDER_UPGRADE ,STRUCTURE_NONE ,TECH_RESEARCH_WEAPONS_TWO ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL ,AND ,CONDITION_NONE ,STRUCTURE_NONE ,TECH_NULL }); + entries.push_back(Build_order_entry{BUILD_ORDER_UPGRADE, STRUCTURE_NONE, TECH_RESEARCH_CATALYSTS, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL, AND, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL}); + entries.push_back(Build_order_entry{BUILD_ORDER_UPGRADE, STRUCTURE_NONE, TECH_RESEARCH_HEAVYARMOR, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL, AND, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL}); + entries.push_back(Build_order_entry{BUILD_ORDER_UPGRADE, STRUCTURE_NONE, TECH_RESEARCH_JETPACKS, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL, AND, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL}); + entries.push_back(Build_order_entry{BUILD_ORDER_UPGRADE, STRUCTURE_NONE, TECH_RESEARCH_ARMOR_THREE, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL, AND, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL}); + entries.push_back(Build_order_entry{BUILD_ORDER_UPGRADE, STRUCTURE_NONE, TECH_RESEARCH_WEAPONS_THREE, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL, AND, CONDITION_NONE, STRUCTURE_NONE, TECH_NULL}); + + defaultBuildOrder.BuildOrder = entries; + defaultBuildOrder.BuildOrderName = "Default Build-Order"; + defaultBuildOrder.Weight = 1.0f; + AIBO_MarineBuildOrders.push_back(defaultBuildOrder); + AIBO_CurrentBuildOrder = &AIBO_MarineBuildOrders[0]; // Set the current build order to the default one +} + +void AIBO_ParseMarineBuildOrder() +{ + BuildMessageAnnounced = false; + AIBO_MarineBuildOrders.clear(); + + std::string BuildOrderFileString = std::string(getModDirectory()) + "/marine_build_orders.txt"; + std::ifstream BuildOrderFile(BuildOrderFileString.c_str()); + + if (!BuildOrderFile.is_open()) + { + g_engfuncs.pfnServerPrint("Failed to open marine build order file. Using default.\n"); + AIBO_LoadHardCodedMarineBuildOrder(); + return; + } + + std::string Line; + Marine_build_order currentOrder; + bool inBuildOrder = false; + + while (std::getline(BuildOrderFile, Line)) + { + // Remove whitespace from both ends + Line.erase(Line.begin(), std::find_if(Line.begin(), Line.end(), [](int ch) { return !std::isspace(ch); })); + Line.erase(std::find_if(Line.rbegin(), Line.rend(), [](int ch) { return !std::isspace(ch); }).base(), Line.end()); + + if (Line.empty() || Line[0] == '#') + continue; + + if (Line == "BuildOrderStart") + { + inBuildOrder = true; + currentOrder = Marine_build_order(); // reset + continue; + } + if (Line == "BuildOrderEnd") + { + if (inBuildOrder) + { + AIBO_MarineBuildOrders.push_back(currentOrder); + inBuildOrder = false; + } + continue; + } + if (!inBuildOrder) + continue; + + if (Line.find("name=") == 0) + { + currentOrder.BuildOrderName = Line.substr(5); + } + else if (Line.find("weight=") == 0) + { + currentOrder.Weight = std::stof(Line.substr(7)); + } + else if (Line.find("initialIPs=") == 0) + { + currentOrder.InitialInfantryPortalCount = std::stoi(Line.substr(11)); + } + else if (Line.find("entry=") == 0) + { + std::string entryLine = Line.substr(6); + + vector entryParts; + entryParts.reserve(7); + std::string entryName; + size_t pos = 0; + int entryCount = 0; + while ((pos = entryLine.find(',')) != std::string::npos) { + entryName = entryLine.substr(0, pos); + entryLine.erase(0, pos + 1); + entryParts.push_back(entryName); + entryCount++; + } + // Add the last part after the last comma + if (!entryLine.empty()) { + entryParts.push_back(entryLine); + entryCount++; + } + if (entryCount != 7) + { + continue; // Invalid entry, skip + } + + Build_order_entry entry; + entry.BuildOrderType = AIBO_MapStringToBuildOrderType(entryParts[0]); + entry.StructureToBuild = (entry.BuildOrderType == BUILD_ORDER_STRUCTURE) ? AIBO_MapStringToStructure(entryParts[1]) : STRUCTURE_NONE; + entry.UpgradeToResearch = (entry.BuildOrderType == BUILD_ORDER_UPGRADE) ? AIBO_MapStringToTech(entryParts[1]) : TECH_NULL; + entry.BuildConditionOne = AIBO_MapStringToBuildCondition(entryParts[2]); + entry.StructureRequiredOne = (entry.BuildConditionOne == STRUCTURE_EXISTS) ? AIBO_MapStringToStructure(entryParts[3]) : STRUCTURE_NONE; + entry.UpgradeRequiredOne = (entry.BuildConditionOne == UPGRADE_EXISTS) ? AIBO_MapStringToTech(entryParts[3]) : TECH_NULL; + entry.Connective = (!stricmp(entryParts[4].c_str(), "or")) ? OR : AND; + entry.BuildConditionTwo = AIBO_MapStringToBuildCondition(entryParts[5]); + entry.StructureRequiredTwo = (entry.BuildConditionTwo == STRUCTURE_EXISTS) ? AIBO_MapStringToStructure(entryParts[6]) : STRUCTURE_NONE; + entry.UpgradeRequiredTwo = (entry.BuildConditionTwo == UPGRADE_EXISTS) ? AIBO_MapStringToTech(entryParts[6]) : TECH_NULL; + if (entry.BuildConditionOne == TIME_ELAPSED) + { + entry.TimeLimitInSecondsOne = std::stoi(entryParts[3]); + std::string message = "TimeLimitOne tried parsing " + entryParts[3] + ". Got " + std::to_string(std::stoi(entryParts[3])) + "\n"; + g_engfuncs.pfnServerPrint(message.c_str()); + } + if (entry.BuildConditionTwo == TIME_ELAPSED) + { + entry.TimeLimitInSecondsTwo = std::stoi(entryParts[6]); + std::string message = "TimeLimitOne tried parsing " + entryParts[6] + ". Got " + std::to_string(std::stoi(entryParts[6])) + "\n"; + g_engfuncs.pfnServerPrint(message.c_str()); + } + currentOrder.BuildOrder.push_back(entry); + } + } + + BuildOrderFile.close(); + +} + + +void AIBO_ResetMarineBuildOrder() { + BuildMessageAnnounced = false; +} + +void AIBO_SelectBuildOrderRandomly() { + if (!bo_rng_initialized) + { + unsigned int seed = static_cast(time(nullptr)); + srand(seed); + bo_rng_initialized = true; + } + + if (AIBO_MarineBuildOrders.empty()) { + AIBO_LoadHardCodedMarineBuildOrder(); + } + + std::vector cumulativeWeights; + cumulativeWeights.reserve(AIBO_MarineBuildOrders.size()); + float sumWeights = 0.0f; + + for (auto& order : AIBO_MarineBuildOrders) + { + sumWeights += order.Weight; + cumulativeWeights.push_back(sumWeights); + } + + float randomValue = AIBO_FloatRandomRange(0.0f, sumWeights); + auto it = std::lower_bound(cumulativeWeights.begin(), cumulativeWeights.end(), randomValue); + + if (it != cumulativeWeights.end()) { + size_t index = std::distance(cumulativeWeights.begin(), it); + AIBO_CurrentBuildOrder = &AIBO_MarineBuildOrders[index]; + } + else { + AIBO_CurrentBuildOrder = &AIBO_MarineBuildOrders.back(); + } + + BuildMessageAnnouncementIndex = rand() % BuildOrderMessageTemplates.size(); + BuildMessageAnnounced = false; +} + +std::string GetBuildOrderMessage() { + if (!AIBO_CurrentBuildOrder) return "No build order selected!"; + const std::string& tmpl = BuildOrderMessageTemplates[BuildMessageAnnouncementIndex]; + size_t pos = tmpl.find("{}"); + if (pos != std::string::npos) { + std::string msg = tmpl; + msg.replace(pos, 2, AIBO_CurrentBuildOrder->BuildOrderName); + return msg; + } + return tmpl; +} + +bool AIBO_BuildOrderIsShotgunRush() { + if (!AIBO_CurrentBuildOrder) return false; + return !stricmp(AIBO_CurrentBuildOrder->BuildOrderName.c_str(), "Shotgun Rush"); +} diff --git a/main/source/mod/AvHAIMarineBuildOrderManager.h b/main/source/mod/AvHAIMarineBuildOrderManager.h new file mode 100644 index 00000000..12fbe804 --- /dev/null +++ b/main/source/mod/AvHAIMarineBuildOrderManager.h @@ -0,0 +1,76 @@ +#pragma once + +#ifndef AVH_AI_MARINE_BUILD_ORDER_H +#define AVH_AI_MARINE_BUILD_ORDER_H + +#include "AvHAIConstants.h" +#include +#include + +typedef enum +{ + BUILD_ORDER_NONE, + BUILD_ORDER_STRUCTURE, + BUILD_ORDER_UPGRADE, +} AvHAIBuildOrderType; + +typedef enum +{ + CONDITION_NONE, + STRUCTURE_EXISTS, + UPGRADE_EXISTS, + TIME_ELAPSED, +} AvHAIBuildCondition; + +typedef enum +{ + OR, + AND, +} AvHAILogicConnective; + +typedef struct _BUILD_ORDER_ENTRY +{ + AvHAIBuildOrderType BuildOrderType = BUILD_ORDER_NONE; + AvHAIDeployableStructureType StructureToBuild = STRUCTURE_NONE; + AvHTechID UpgradeToResearch = TECH_NULL; + AvHAIBuildCondition BuildConditionOne = CONDITION_NONE; + AvHAIDeployableStructureType StructureRequiredOne = STRUCTURE_NONE; + AvHTechID UpgradeRequiredOne = TECH_NULL; + AvHAILogicConnective Connective = AND; + AvHAIBuildCondition BuildConditionTwo = CONDITION_NONE; + AvHAIDeployableStructureType StructureRequiredTwo = STRUCTURE_NONE; + AvHTechID UpgradeRequiredTwo = TECH_NULL; + int TimeLimitInSecondsOne = 0; + int TimeLimitInSecondsTwo = 0; +} Build_order_entry; + +typedef struct _MARINE_BUILD_ORDER { + std::vector BuildOrder = {}; + std::string BuildOrderName = ""; + float Weight = 1.0f; + int InitialInfantryPortalCount = 1; +} Marine_build_order; + +extern std::vector AIBO_MarineBuildOrders; +extern Marine_build_order* AIBO_CurrentBuildOrder; +extern vector BuildOrderMessages; +extern bool BuildMessageAnnounced; +extern int BuildMessageAnnouncementIndex; + +void AIBO_LoadHardCodedMarineBuildOrder(); +void AIBO_ParseMarineBuildOrder(); +void AIBO_ResetMarineBuildOrder(); +void AIBO_SelectBuildOrderRandomly(); +std::string GetBuildOrderMessage(); +bool AIBO_BuildOrderIsShotgunRush(); +int AIBO_IntRandomRange(int MinValue, int MaxValue); +float AIBO_FloatRandomRange(float MinValue, float MaxValue); +AvHAIDeployableStructureType AIBO_MapStringToStructure(const std::string& StructureName); +AvHAIDeployableStructureType AIBO_MapTechToRequiredStructure(AvHTechID TechID); +AvHAIBuildCondition AIBO_MapStringToBuildCondition(const std::string& ConditionName); +AvHAIBuildOrderType AIBO_MapStringToBuildOrderType(const std::string& BuildOrderTypeName); +AvHMessageID AIBO_MapTechIDToMessageID(AvHTechID TechID); +int AIBO_GetResearchCost(AvHTechID TechID); +AvHTechID AIBO_MapStringToTech(const std::string& TechName); + +#endif \ No newline at end of file diff --git a/main/source/mod/AvHAIPlayer.cpp b/main/source/mod/AvHAIPlayer.cpp index 04ed05ba..3dbcb673 100644 --- a/main/source/mod/AvHAIPlayer.cpp +++ b/main/source/mod/AvHAIPlayer.cpp @@ -3,7 +3,6 @@ #include "AvHAIPlayerUtil.h" #include "AvHAIHelper.h" #include "AvHAIMath.h" -#include "AvHAIHelper.h" #include "AvHAINavigation.h" #include "AvHAIWeaponHelper.h" #include "AvHAITactical.h" diff --git a/main/source/mod/AvHAIPlayerManager.cpp b/main/source/mod/AvHAIPlayerManager.cpp index 055199df..5a4e33fd 100644 --- a/main/source/mod/AvHAIPlayerManager.cpp +++ b/main/source/mod/AvHAIPlayerManager.cpp @@ -4,6 +4,7 @@ #include "AvHAITactical.h" #include "AvHAINavigation.h" #include "AvHAIConfig.h" +#include "AvHAIMarineBuildOrderManager.h" #include "AvHAIWeaponHelper.h" #include "AvHAIHelper.h" #include "AvHAICommander.h" @@ -943,6 +944,7 @@ void AIMGR_ResetRound() CountdownStartedTime = 0.0f; AITAC_DetermineRelocationEnabled(); + AIBO_SelectBuildOrderRandomly(); } void AIMGR_ReloadNavigationData() @@ -1053,6 +1055,8 @@ void AIMGR_NewMap() CONFIG_ParseConfigFile(); CONFIG_PopulateBotNames(); + AIBO_ParseMarineBuildOrder(); + AIBO_SelectBuildOrderRandomly(); } bool AIMGR_IsNavmeshLoaded() @@ -1417,6 +1421,10 @@ void AIMGR_OnBotEnabled() CONFIG_ParseConfigFile(); CONFIG_PopulateBotNames(); + if (!AIBO_CurrentBuildOrder) + { + AIBO_SelectBuildOrderRandomly(); + } AITAC_ClearMapAIData(true); @@ -1468,6 +1476,7 @@ void AIMGR_OnBotDisabled() UnloadNavigationData(); } + AIBO_ResetMarineBuildOrder(); bBotsEnabled = false; }