From c6987f4183e3b00a647fae35b590ee23e3846b09 Mon Sep 17 00:00:00 2001
From: Laserlicht <13953785+Laserlicht@users.noreply.github.com>
Date: Sun, 9 Feb 2025 14:49:28 +0100
Subject: [PATCH] upgrade all radial wheel button

---
 .../Sprites/radialMenu/upgradeCreatures.png   | Bin 0 -> 567 bytes
 Mods/vcmi/Content/config/english.json         |   5 +
 Mods/vcmi/Content/config/german.json          |   5 +
 client/windows/CCastleInterface.cpp           |  91 +++++++++++++++++-
 client/windows/CCastleInterface.h             |   2 +
 5 files changed, 98 insertions(+), 5 deletions(-)
 create mode 100644 Mods/vcmi/Content/Sprites/radialMenu/upgradeCreatures.png

diff --git a/Mods/vcmi/Content/Sprites/radialMenu/upgradeCreatures.png b/Mods/vcmi/Content/Sprites/radialMenu/upgradeCreatures.png
new file mode 100644
index 0000000000000000000000000000000000000000..37538e1f7599d74556da11c8bba83a80f0ef6c3f
GIT binary patch
literal 567
zcmeAS@N?(olHy`uVBq!ia0vp^u0ZV1!3HGvigEY@DYhhUcNd2LAh=-f^2rPgjBh+$
z978JRyuI$|ciTaP?ZJOl)=t*DUl>%=8h1Tpv2|L1LC-;<>AdiQp#BTV1x#XFxRc7e
zk1(y&7I>0wD3O%jlk`WY=S%%=ACdE??H@lkR8T?#mt<dmdSvsztaa=8`3;geC3jxN
z%(K#*a&yK8$3LfU+U9&-a-H#N$UT|mzgZa4GHVqW3?!O<DrT>ry!_((S$*3d3UPg&
zBC)37vTc-6WRm!e?t^<wx6c(?w~Z^VW_j;4?q#bYzP@^8E}Or)RO8ICr%i402fO8u
z{QWAl|L)7Dd6{=V{Loibs$aV+dWNh}*vvkM!re#1Zo3I*Tix68Z^kw2qh@MiU7FV;
z-Y7iNoVk3<lZ=fI=GishInBj;MJ&v?MsD*SzLwroiH81*ZCAdmke1)07s3*tHNkj_
z{Q0T9KmWg3fBnzA7^j){1wTKG`1DEh+BX0F6=&X`Rh_cynE0`dduIJodE3u#zMwwi
ztzDZ4lV;xP<sb6&-~W03?V;GI8vT{FlhTvLcAwf-x4z=++p~L@yPVBC)VMBV7mI~$
zLVP?=ZU4`}mfU|vJlE&D>CBX1JN#~W-BP})Q!@7sPMR-P{qwV3bj2tBqlYxb87jVg
v^%r0NEw;E?CH(jd+qRGIlmt;h<QIm%n7eu3td}+bQv!phtDnm{r-UW|S^fe{

literal 0
HcmV?d00001

diff --git a/Mods/vcmi/Content/config/english.json b/Mods/vcmi/Content/config/english.json
index 8384c2009..0b4a22d92 100644
--- a/Mods/vcmi/Content/config/english.json
+++ b/Mods/vcmi/Content/config/english.json
@@ -68,6 +68,7 @@
 	"vcmi.radialWheel.heroGetArtifacts" : "Get artifacts from other hero",
 	"vcmi.radialWheel.heroSwapArtifacts" : "Swap artifacts with other hero",
 	"vcmi.radialWheel.heroDismiss" : "Dismiss hero",
+	"vcmi.radialWheel.upgradeCreatures" : "Upgrade all creatures",
 
 	"vcmi.radialWheel.moveTop" : "Move to top",
 	"vcmi.radialWheel.moveUp" : "Move up",
@@ -415,6 +416,10 @@
 	"vcmi.townStructure.bank.borrow" : "You enter the bank. A banker sees you and says: \"We have made a special offer for you. You can take a loan of 2500 gold from us for 5 days. You will have to repay 500 gold every day.\"",
 	"vcmi.townStructure.bank.payBack" : "You enter the bank. A banker sees you and says: \"You have already got your loan. Pay it back before taking a new one.\"",
 
+	"vcmi.townWindow.upgradeAll.upgradable" : "Do you want to upgrade following creatures?",
+	"vcmi.townWindow.upgradeAll.notAllUpgradable" : "Not enough resources to upgrade all creatures. Do you want to upgrade following creatures?",
+	"vcmi.townWindow.upgradeAll.notUpgradable" : "Not enough resources to upgrade any creature.",
+
 	"vcmi.logicalExpressions.anyOf"  : "Any of the following:",
 	"vcmi.logicalExpressions.allOf"  : "All of the following:",
 	"vcmi.logicalExpressions.noneOf" : "None of the following:",
diff --git a/Mods/vcmi/Content/config/german.json b/Mods/vcmi/Content/config/german.json
index aa34553b1..ff9ba71f0 100644
--- a/Mods/vcmi/Content/config/german.json
+++ b/Mods/vcmi/Content/config/german.json
@@ -68,6 +68,7 @@
 	"vcmi.radialWheel.heroGetArtifacts" : "Artefakte von anderen Helden erhalten",
 	"vcmi.radialWheel.heroSwapArtifacts" : "Tausche Artefakte mit anderen Helden",
 	"vcmi.radialWheel.heroDismiss" : "Held entlassen",
+	"vcmi.radialWheel.upgradeCreatures" : "Alle Kreaturen aufrüsten",
 
 	"vcmi.radialWheel.moveTop" : "Ganz nach oben bewegen",
 	"vcmi.radialWheel.moveUp" : "Nach oben bewegen",
@@ -415,6 +416,10 @@
 	"vcmi.townStructure.bank.borrow" : "Ihr betretet die Bank. Ein Bankangestellter sieht Euch und sagt: \"Wir haben ein spezielles Angebot für Euch gemacht. Ihr könnt bei uns einen Kredit von 2500 Gold für 5 Tage aufnehmen. Ihr werdet jeden Tag 500 Gold zurückzahlen müssen.\"",
 	"vcmi.townStructure.bank.payBack" : "Ihr betretet die Bank. Ein Bankangestellter sieht Euch und sagt: \"Ihr habt Euren Kredit bereits erhalten. Zahlt Ihn ihn zurück, bevor Ihr einen neuen aufnehmt.\"",
 
+	"vcmi.townWindow.upgradeAll.upgradable" : "Folgende Kreaturen aufrüsten?",
+	"vcmi.townWindow.upgradeAll.notAllUpgradable" : "Nicht genügend Ressourcen um alle Kreaturen aufzurüsten. Folgende Kreaturen aufrüsten?",
+	"vcmi.townWindow.upgradeAll.notUpgradable" : "Nicht genügend Ressourcen um mindestens eine Kreatur aufzurüsten.",
+
 	"vcmi.logicalExpressions.anyOf"  : "Eines der folgenden:",
 	"vcmi.logicalExpressions.allOf"  : "Alles der folgenden:",
 	"vcmi.logicalExpressions.noneOf" : "Keines der folgenden:",
diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp
index 4806e2fb4..4022c8ae3 100644
--- a/client/windows/CCastleInterface.cpp
+++ b/client/windows/CCastleInterface.cpp
@@ -51,6 +51,7 @@
 #include "../../lib/IGameSettings.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/GameConstants.h"
+#include "../../lib/gameState/UpgradeInfo.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/campaign/CampaignState.h"
 #include "../../lib/entities/building/CBuilding.h"
@@ -337,17 +338,92 @@ CHeroGSlot::CHeroGSlot(int x, int y, int updown, const CGHeroInstance * h, HeroS
 
 CHeroGSlot::~CHeroGSlot() = default;
 
+auto CHeroGSlot::getUpgradableSlots(const CArmedInstance *obj)
+{
+	struct result { bool isCreatureUpgradePossible; bool canAffordAny; bool canAffordAll; TResources totalCosts; std::vector<std::pair<SlotID, UpgradeInfo>> upgradeInfos; };
+
+	auto slots = std::map<SlotID, const CStackInstance*>(obj->Slots().begin(), obj->Slots().end());
+	std::vector<std::pair<SlotID, UpgradeInfo>> upgradeInfos;
+	for(auto & slot : slots)
+	{
+		auto upgradeInfo = std::make_pair(slot.first, UpgradeInfo(slot.second->getCreatureID()));
+		LOCPLINT->cb->fillUpgradeInfo(slot.second->armyObj, slot.first, upgradeInfo.second);
+		bool canUpgrade = obj->tempOwner == LOCPLINT->playerID && upgradeInfo.second.canUpgrade();
+		if(canUpgrade)
+			upgradeInfos.push_back(upgradeInfo);
+	}
+
+	std::sort(upgradeInfos.begin(), upgradeInfos.end(), [&](const std::pair<SlotID, UpgradeInfo> & lhs, const std::pair<SlotID, UpgradeInfo> & rhs) {
+		return lhs.second.oldID.toCreature()->getLevel() > rhs.second.oldID.toCreature()->getLevel();
+	});
+	bool creaturesToUpgrade = static_cast<bool>(upgradeInfos.size());
+
+	TResources costs = TResources();
+	std::vector<SlotID> slotInfosToDelete;
+	for(auto & upgradeInfo : upgradeInfos)
+	{
+		TResources upgradeCosts = upgradeInfo.second.getUpgradeCosts() * slots[upgradeInfo.first]->getCount();
+		if(LOCPLINT->cb->getResourceAmount().canAfford(costs + upgradeCosts))
+			costs += upgradeCosts;
+		else
+			slotInfosToDelete.push_back(upgradeInfo.first);
+	}
+	upgradeInfos.erase(std::remove_if(upgradeInfos.begin(), upgradeInfos.end(), [&slotInfosToDelete](const auto& item) {
+		return std::count(slotInfosToDelete.begin(), slotInfosToDelete.end(), item.first);
+	}), upgradeInfos.end());
+
+    return result { creaturesToUpgrade, static_cast<bool>(upgradeInfos.size()), !slotInfosToDelete.size(), costs, upgradeInfos };
+}
+
 void CHeroGSlot::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
 {
 	if(!on)
 		return;
 
-	if(!hero)
+	const CArmedInstance *obj = hero;
+	if(upg == 0 && !obj)
+		obj = owner->town->getUpperArmy();
+	if(!obj)
 		return;
 
+	auto upgradableSlots = getUpgradableSlots(obj);
+	auto upgradeAll = [upgradableSlots, obj](){
+		if(!upgradableSlots.canAffordAny)
+		{
+			LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.townWindow.upgradeAll.notUpgradable"));
+			return;
+		}
+
+		std::vector<std::shared_ptr<CComponent>> resComps;
+		for(TResources::nziterator i(upgradableSlots.totalCosts); i.valid(); i++)
+			resComps.push_back(std::make_shared<CComponent>(ComponentType::RESOURCE, i->resType, i->resVal));
+		resComps.back()->newLine = true;
+		for(auto & upgradeInfo : upgradableSlots.upgradeInfos)
+			resComps.push_back(std::make_shared<CComponent>(ComponentType::CREATURE, upgradeInfo.second.getUpgrade(), std::nullopt));
+			
+		std::string textID = upgradableSlots.canAffordAll ? "vcmi.townWindow.upgradeAll.upgradable" : "vcmi.townWindow.upgradeAll.notAllUpgradable";
+
+		LOCPLINT->showYesNoDialog(CGI->generaltexth->translate(textID), [upgradableSlots, obj](){
+			for(auto & upgradeInfo : upgradableSlots.upgradeInfos)
+				LOCPLINT->cb->upgradeCreature(obj, upgradeInfo.first, upgradeInfo.second.getUpgrade());
+		}, nullptr, resComps);
+	};
+
 	if (!settings["input"]["radialWheelGarrisonSwipe"].Bool())
 		return;
 
+	if(!hero)
+	{
+		if(upgradableSlots.isCreatureUpgradePossible)
+		{
+			std::vector<RadialMenuConfig> menuElements = {
+				{ RadialMenuConfig::ITEM_WW, true, "upgradeCreatures", "vcmi.radialWheel.upgradeCreatures", [upgradeAll](){ upgradeAll(); } },
+			};
+			GH.windows().createAndPushWindow<RadialMenu>(pos.center(), menuElements);
+		}
+		return;
+	}
+
 	std::shared_ptr<CHeroGSlot> other = upg ? owner->garrisonedHero : owner->visitingHero;
 
 	bool twoHeroes = hero && other->hero;
@@ -361,11 +437,16 @@ void CHeroGSlot::gesture(bool on, const Point & initialPosition, const Point & f
 		{ RadialMenuConfig::ITEM_EE, twoHeroes, "tradeHeroes", "vcmi.radialWheel.heroExchange", [heroId, heroOtherId](){LOCPLINT->showHeroExchange(heroId, heroOtherId);} },
 		{ RadialMenuConfig::ITEM_SW, twoHeroes, "moveArtifacts", "vcmi.radialWheel.heroGetArtifacts", [heroId, heroOtherId](){CExchangeController(heroId, heroOtherId).moveArtifacts(false, true, true);} },
 		{ RadialMenuConfig::ITEM_SE, twoHeroes, "swapArtifacts", "vcmi.radialWheel.heroSwapArtifacts", [heroId, heroOtherId](){CExchangeController(heroId, heroOtherId).swapArtifacts(true, true);} },
-		{ RadialMenuConfig::ITEM_WW, true, "dismissHero", "vcmi.radialWheel.heroDismiss", [this]()
+		{ RadialMenuConfig::ITEM_WW, true, !upgradableSlots.isCreatureUpgradePossible ? "dismissHero" : "upgradeCreatures", !upgradableSlots.isCreatureUpgradePossible ? "vcmi.radialWheel.heroDismiss" : "vcmi.radialWheel.upgradeCreatures", [this, upgradableSlots, upgradeAll]()
 		{
-			CFunctionList<void()> ony = [=](){ };
-			ony += [=](){ LOCPLINT->cb->dismissHero(hero); };
-			LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[22], ony, nullptr);
+			if(upgradableSlots.isCreatureUpgradePossible)
+				upgradeAll();
+			else
+			{
+				CFunctionList<void()> ony = [=](){ };
+				ony += [=](){ LOCPLINT->cb->dismissHero(hero); };
+				LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[22], ony, nullptr);
+			}
 		} },
 	};
 
diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h
index 08672285a..b985db6c1 100644
--- a/client/windows/CCastleInterface.h
+++ b/client/windows/CCastleInterface.h
@@ -103,6 +103,8 @@ class CHeroGSlot : public CIntObject
 	const CGHeroInstance * hero;
 	int upg; //0 - up garrison, 1 - down garrison
 
+	auto getUpgradableSlots(const CArmedInstance *obj);
+
 public:
 	CHeroGSlot(int x, int y, int updown, const CGHeroInstance *h, HeroSlots * Owner);
 	~CHeroGSlot();