From d04d6182536c4bea0d12dcc8c4c7460b888fb61a Mon Sep 17 00:00:00 2001
From: Tim Felgentreff <timfelgentreff@gmail.com>
Date: Sat, 18 Dec 2021 14:35:13 +0100
Subject: [PATCH] force the AI to honour single-research vs multi-research and
 document that a bit

---
 src/action/action_research.cpp |  6 ++--
 src/ai/ai_local.h              | 11 +++++-
 src/ai/ai_resource.cpp         | 62 +++++++++++++++++++++++-----------
 src/ai/script_ai.cpp           | 10 ++++--
 4 files changed, 64 insertions(+), 25 deletions(-)

diff --git a/src/action/action_research.cpp b/src/action/action_research.cpp
index 8a4571a18..992c24cfb 100644
--- a/src/action/action_research.cpp
+++ b/src/action/action_research.cpp
@@ -132,8 +132,10 @@
 #endif
 	CPlayer &player = *unit.Player;
 	if (player.UpgradeTimers.Upgrades[upgrade.ID] >= upgrade.Costs[TimeCost]) {
-		// completed in between our last step, possibly due to another unit finishing the upgrade
-		// n.b.: this is a feature: you can speed up research by using multiple buildings!
+		// completed in between our last step, possibly due to another unit
+		// finishing the upgrade n.b.: this is a feature: you can speed up
+		// research by using multiple buildings! This feature is only available
+		// if the button is marked "check-research", not "check-single-research"
 		this->Finished = true;
 		return;
 	}
diff --git a/src/ai/ai_local.h b/src/ai/ai_local.h
index b3dc6a036..cd82ca6c1 100644
--- a/src/ai/ai_local.h
+++ b/src/ai/ai_local.h
@@ -345,10 +345,19 @@ public:
 	std::vector<std::vector<CUnitType *> > Upgrade;
 	/**
 	** The index is the research that should be made, giving a table of all
-	** units/buildings which could research this upgrade.
+	** units/buildings which could research this upgrade. This table only
+	** includes those unit types which have the research defined as a button
+	** without the "check-single-research" restriction.
 	*/
 	std::vector<std::vector<CUnitType *> > Research;
 	/**
+	** The index is the research that should be made, giving a table of all
+	** units/buildings which could research this upgrade. This table only
+	** includes those unit types which have the research defined as a button
+	** with the "check-single-research" restriction.
+	*/
+	std::vector<std::vector<CUnitType *> > SingleResearch;
+	/**
 	** The index is the unit that should be repaired, giving a table of all
 	** units/buildings which could repair this unit.
 	*/
diff --git a/src/ai/ai_resource.cpp b/src/ai/ai_resource.cpp
index 0f1fb7a3a..6ca25d3f3 100644
--- a/src/ai/ai_resource.cpp
+++ b/src/ai/ai_resource.cpp
@@ -747,29 +747,51 @@ void AiAddResearchRequest(CUpgrade *upgrade)
 	//
 	// Check if we have a place for the upgrade to research
 	//
-	const int n = AiHelpers.Research.size();
-	std::vector<std::vector<CUnitType *> > &tablep = AiHelpers.Research;
+	{ // multi-research case
+		const int n = AiHelpers.Research.size();
+		std::vector<std::vector<CUnitType *> > &tablep = AiHelpers.Research;
 
-	if (upgrade->ID > n) { // Oops not known.
-		DebugPrint("%d: AiAddResearchRequest I: Nothing known about '%s'\n"
-				   _C_ AiPlayer->Player->Index _C_ upgrade->Ident.c_str());
-		return;
-	}
-	std::vector<CUnitType *> &table = tablep[upgrade->ID];
-	if (table.empty()) { // Oops not known.
-		DebugPrint("%d: AiAddResearchRequest II: Nothing known about '%s'\n"
-				   _C_ AiPlayer->Player->Index _C_ upgrade->Ident.c_str());
-		return;
-	}
-
-	const int *unit_count = AiPlayer->Player->UnitTypesAiActiveCount;
-	for (unsigned int i = 0; i < table.size(); ++i) {
-		// The type is available
-		if (unit_count[table[i]->Slot]
-			&& AiResearchUpgrade(*table[i], *upgrade)) {
-			return;
+		if (upgrade->ID < n) { // not known as multi-researchable upgrade
+			std::vector<CUnitType *> &table = tablep[upgrade->ID];
+			if (!table.empty()) { // not known as multi-researchable upgrade
+				const int *unit_count = AiPlayer->Player->UnitTypesAiActiveCount;
+				for (unsigned int i = 0; i < table.size(); ++i) {
+					// The type is available
+					if (unit_count[table[i]->Slot]
+						&& AiResearchUpgrade(*table[i], *upgrade)) {
+						return;
+					}
+				}
+				return;
+			}
 		}
 	}
+	{ // single-research case
+		const int n = AiHelpers.SingleResearch.size();
+		std::vector<std::vector<CUnitType *> > &tablep = AiHelpers.SingleResearch;
+
+		if (upgrade->ID < n) { // not known
+			std::vector<CUnitType *> &table = tablep[upgrade->ID];
+			if (!table.empty()) { // not known
+				// known as a single-research upgrade, check if we're already
+				// researching it. if so, ignore this request.
+				if (AiPlayer->Player->UpgradeTimers.Upgrades[upgrade->ID]) {
+					return;
+				}
+				const int *unit_count = AiPlayer->Player->UnitTypesAiActiveCount;
+				for (unsigned int i = 0; i < table.size(); ++i) {
+					// The type is available
+					if (unit_count[table[i]->Slot]
+						&& AiResearchUpgrade(*table[i], *upgrade)) {
+						return;
+					}
+				}
+				return;
+			}
+		}
+	}
+	DebugPrint("%d: AiAddResearchRequest I: Nothing known about '%s'\n"
+			   _C_ AiPlayer->Player->Index _C_ upgrade->Ident.c_str());
 }
 
 /**
diff --git a/src/ai/script_ai.cpp b/src/ai/script_ai.cpp
index 71348e25e..f59fdbef0 100644
--- a/src/ai/script_ai.cpp
+++ b/src/ai/script_ai.cpp
@@ -286,8 +286,14 @@ static void InitAiHelper(AiHelper &aiHelper)
 			case ButtonResearch : {
 				int researchId = UpgradeIdByIdent(button.ValueStr);
 
-				for (std::vector<CUnitType *>::const_iterator j = unitmask.begin(); j != unitmask.end(); ++j) {
-					AiHelperInsert(aiHelper.Research, researchId, **j);
+				if (button.Allowed == ButtonCheckSingleResearch) {
+					for (std::vector<CUnitType *>::const_iterator j = unitmask.begin(); j != unitmask.end(); ++j) {
+						AiHelperInsert(aiHelper.SingleResearch, researchId, **j);
+					}
+				} else {
+					for (std::vector<CUnitType *>::const_iterator j = unitmask.begin(); j != unitmask.end(); ++j) {
+						AiHelperInsert(aiHelper.Research, researchId, **j);
+					}
 				}
 				break;
 			}