Browse Source

Merge pull request #3152 from SoundSSGood/CAltar-widget

Altar rework
Ivan Savenko 1 year ago
parent
commit
0b4fa20bdc

+ 6 - 0
client/CMakeLists.txt

@@ -93,12 +93,14 @@ set(client_SRCS
 	renderSDL/SDL_Extensions.cpp
 
 	widgets/Buttons.cpp
+	widgets/CAltar.cpp
 	widgets/CArtifactHolder.cpp
 	widgets/CComponent.cpp
 	widgets/CExchangeController.cpp
 	widgets/CGarrisonInt.cpp
 	widgets/CreatureCostBox.cpp
 	widgets/ComboBox.cpp
+	widgets/CTradeBase.cpp
 	widgets/Images.cpp
 	widgets/MiscWidgets.cpp
 	widgets/ObjectLists.cpp
@@ -114,6 +116,7 @@ set(client_SRCS
 	widgets/CWindowWithArtifacts.cpp
 	widgets/RadialMenu.cpp
 
+	windows/CAltarWindow.cpp
 	windows/CCastleInterface.cpp
 	windows/CCreatureWindow.cpp
 	windows/CHeroOverview.cpp
@@ -259,12 +262,14 @@ set(client_HEADERS
 	renderSDL/SDL_PixelAccess.h
 
 	widgets/Buttons.h
+	widgets/CAltar.h
 	widgets/CArtifactHolder.h
 	widgets/CComponent.h
 	widgets/CExchangeController.h
 	widgets/CGarrisonInt.h
 	widgets/CreatureCostBox.h
 	widgets/ComboBox.h
+	widgets/CTradeBase.h
 	widgets/Images.h
 	widgets/MiscWidgets.h
 	widgets/ObjectLists.h
@@ -280,6 +285,7 @@ set(client_HEADERS
 	widgets/CWindowWithArtifacts.h
 	widgets/RadialMenu.h
 
+	windows/CAltarWindow.h
 	windows/CCastleInterface.h
 	windows/CCreatureWindow.h
 	windows/CHeroOverview.h

+ 5 - 4
client/CPlayerInterface.cpp

@@ -48,6 +48,7 @@
 #include "widgets/CComponent.h"
 #include "widgets/CGarrisonInt.h"
 
+#include "windows/CAltarWindow.h"
 #include "windows/CCastleInterface.h"
 #include "windows/CCreatureWindow.h"
 #include "windows/CHeroWindow.h"
@@ -421,7 +422,7 @@ void CPlayerInterface::heroPrimarySkillChanged(const CGHeroInstance * hero, Prim
 	if (which == PrimarySkill::EXPERIENCE)
 	{
 		for (auto ctw : GH.windows().findWindows<CAltarWindow>())
-			ctw->setExpToLevel();
+			ctw->updateExpToLevel();
 	}
 	else
 		adventureInt->onHeroChanged(hero);
@@ -558,10 +559,10 @@ void CPlayerInterface::garrisonsChanged(std::vector<const CGObjectInstance *> ob
 	for (auto cgh : GH.windows().findWindows<IGarrisonHolder>())
 		cgh->updateGarrisons();
 
-	for (auto cmw : GH.windows().findWindows<CTradeWindow>())
+	for (auto cmw : GH.windows().findWindows<CAltarWindow>())
 	{
-		if (vstd::contains(objs, cmw->hero))
-			cmw->garrisonChanged();
+		if(vstd::contains(objs, cmw->getHero()))
+			cmw->updateGarrison();
 	}
 
 	GH.windows().totalRedraw();

+ 465 - 0
client/widgets/CAltar.cpp

@@ -0,0 +1,465 @@
+/*
+ * CAltarWindow.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "CAltar.h"
+
+#include "../widgets/CAltar.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/CursorHandler.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/Slider.h"
+#include "../widgets/TextControls.h"
+
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+
+#include "../../CCallback.h"
+
+#include "../../lib/networkPacks/ArtifactLocation.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapObjects/CGMarket.h"
+
+CAltar::CAltar(const IMarket * market, const CGHeroInstance * hero)
+	: CTradeBase(market, hero)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+
+	// Experience needed to reach next level
+	texts.emplace_back(std::make_shared<CTextBox>(CGI->generaltexth->allTexts[475], Rect(15, 415, 125, 50), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW));
+	// Total experience on the Altar
+	texts.emplace_back(std::make_shared<CTextBox>(CGI->generaltexth->allTexts[476], Rect(15, 495, 125, 40), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW));
+	deal = std::make_shared<CButton>(Point(269, 520), AnimationPath::builtin("ALTSACR.DEF"), CGI->generaltexth->zelp[585], std::bind(&CAltar::makeDeal, this));
+	expToLevel = std::make_shared<CLabel>(75, 477, FONT_SMALL, ETextAlignment::CENTER);
+	expForHero = std::make_shared<CLabel>(75, 545, FONT_SMALL, ETextAlignment::CENTER);
+}
+
+void CAltar::deselect()
+{
+	hLeft = hRight = nullptr;
+	deal->block(true);
+}
+
+CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance * hero)
+	: CAltar(market, hero)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
+
+	labels.emplace_back(std::make_shared<CLabel>(450, 34, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[477]));
+	labels.emplace_back(std::make_shared<CLabel>(302, 423, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[478]));
+	selectedCost = std::make_shared<CLabel>(302, 500, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
+	selectedArt = std::make_shared<CArtPlace>(Point(280, 442));
+
+	sacrificeAllButton = std::make_shared<CButton>(Point(393, 520), AnimationPath::builtin("ALTFILL.DEF"),
+		CGI->generaltexth->zelp[571], std::bind(&CAltar::sacrificeAll, this));
+	sacrificeAllButton->block(hero->artifactsInBackpack.empty() && hero->artifactsWorn.empty());
+
+	sacrificeBackpackButton = std::make_shared<CButton>(Point(147, 520), AnimationPath::builtin("ALTEMBK.DEF"),
+		CGI->generaltexth->zelp[570], std::bind(&CAltarArtifacts::sacrificeBackpack, this));
+	sacrificeBackpackButton->block(hero->artifactsInBackpack.empty());
+
+	arts = std::make_shared<CArtifactsOfHeroAltar>(Point(-365, -11));
+	arts->setHero(hero);
+
+	int slotNum = 0;
+	for(auto & altarSlotPos : posSlotsAltar)
+	{
+		auto altarSlot = std::make_shared<CTradeableItem>(altarSlotPos, EType::ARTIFACT_PLACEHOLDER, -1, false, slotNum++);
+		altarSlot->clickPressedCallback = std::bind(&CAltarArtifacts::onSlotClickPressed, this, _1);
+		altarSlot->subtitle = "";
+		items.front().emplace_back(altarSlot);
+	}
+
+	calcExpAltarForHero();
+	deselect();
+};
+
+TExpType CAltarArtifacts::calcExpAltarForHero()
+{
+	auto artifactsOfHero = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
+	TExpType expOnAltar(0);
+	for(const auto art : artifactsOfHero->artifactsOnAltar)
+	{
+		int dmp, expOfArt;
+		market->getOffer(art->artType->getId(), 0, dmp, expOfArt, EMarketMode::ARTIFACT_EXP);
+		expOnAltar += expOfArt;
+	}
+	auto resultExp = hero->calculateXp(expOnAltar);
+	expForHero->setText(std::to_string(resultExp));
+	return resultExp;
+}
+
+void CAltarArtifacts::makeDeal()
+{
+	std::vector<ui32> positions;
+	for(const auto art : arts->artifactsOnAltar)
+	{
+		positions.push_back(hero->getSlotByInstance(art));
+	}
+	std::sort(positions.begin(), positions.end(), std::greater<>());
+
+	LOCPLINT->cb->trade(market, EMarketMode::ARTIFACT_EXP, positions, {}, {}, hero);
+	arts->artifactsOnAltar.clear();
+
+	for(auto item : items[0])
+	{
+		item->setID(-1);
+		item->subtitle = "";
+	}
+	deal->block(true);
+	calcExpAltarForHero();
+}
+
+void CAltarArtifacts::sacrificeAll()
+{
+	std::vector<ConstTransitivePtr<CArtifactInstance>> artsForMove;
+	for(const auto & slotInfo : arts->getHero()->artifactsWorn)
+	{
+		if(!slotInfo.second.locked && slotInfo.second.artifact->artType->isTradable())
+			artsForMove.push_back(slotInfo.second.artifact);
+	}
+	for(auto artInst : artsForMove)
+		moveArtToAltar(nullptr, artInst);
+	arts->updateWornSlots();
+	sacrificeBackpack();
+}
+
+void CAltarArtifacts::sacrificeBackpack()
+{
+	while(!arts->visibleArtSet.artifactsInBackpack.empty())
+	{
+		if(!putArtOnAltar(nullptr, arts->visibleArtSet.artifactsInBackpack[0].artifact))
+			break;
+	};
+	calcExpAltarForHero();
+}
+
+void CAltarArtifacts::setSelectedArtifact(const CArtifactInstance * art)
+{
+	if(art)
+	{
+		selectedArt->setArtifact(art);
+		int dmp, exp;
+		market->getOffer(art->getTypeId(), 0, dmp, exp, EMarketMode::ARTIFACT_EXP);
+		selectedCost->setText(std::to_string(hero->calculateXp(exp)));
+	}
+	else
+	{
+		selectedArt->setArtifact(nullptr);
+		selectedCost->setText("");
+	}
+}
+
+void CAltarArtifacts::moveArtToAltar(std::shared_ptr<CTradeableItem> altarSlot, const CArtifactInstance * art)
+{
+	if(putArtOnAltar(altarSlot, art))
+	{
+		CCS->curh->dragAndDropCursor(nullptr);
+		arts->unmarkSlots();
+	}
+}
+
+std::shared_ptr<CArtifactsOfHeroAltar> CAltarArtifacts::getAOHset() const
+{
+	return arts;
+}
+
+bool CAltarArtifacts::putArtOnAltar(std::shared_ptr<CTradeableItem> altarSlot, const CArtifactInstance * art)
+{
+	if(!art->artType->isTradable())
+	{
+		logGlobal->warn("Cannot put special artifact on altar!");
+		return false;
+	}
+
+	if(!altarSlot || altarSlot->id != -1)
+	{
+		int slotIndex = -1;
+		while(items[0][++slotIndex]->id >= 0 && slotIndex + 1 < items[0].size());
+		slotIndex = items[0][slotIndex]->id == -1 ? slotIndex : -1;
+		if(slotIndex < 0)
+		{
+			logGlobal->warn("No free slots on altar!");
+			return false;
+		}
+		altarSlot = items[0][slotIndex];
+	}
+
+	int dmp, exp;
+	market->getOffer(art->artType->getId(), 0, dmp, exp, EMarketMode::ARTIFACT_EXP);
+	exp = static_cast<int>(hero->calculateXp(exp));
+
+	arts->artifactsOnAltar.insert(art);
+	altarSlot->setArtInstance(art);
+	altarSlot->subtitle = std::to_string(exp);
+
+	deal->block(false);
+	return true;
+};
+
+void CAltarArtifacts::onSlotClickPressed(std::shared_ptr<CTradeableItem> altarSlot)
+{
+	const auto pickedArtInst = arts->getPickedArtifact();
+	if(pickedArtInst)
+	{
+		arts->pickedArtMoveToAltar(ArtifactPosition::TRANSITION_POS);
+		moveArtToAltar(altarSlot, pickedArtInst);
+	}
+	else if(const CArtifactInstance * art = altarSlot->getArtInstance())
+	{
+		const auto hero = arts->getHero();
+		const auto slot = hero->getSlotByInstance(art);
+		assert(slot != ArtifactPosition::PRE_FIRST);
+		LOCPLINT->cb->swapArtifacts(ArtifactLocation(hero->id, slot),
+			ArtifactLocation(hero->id, ArtifactPosition::TRANSITION_POS));
+		arts->pickedArtFromSlot = slot;
+		arts->artifactsOnAltar.erase(art);
+		altarSlot->setID(-1);
+		altarSlot->subtitle.clear();
+		deal->block(!arts->artifactsOnAltar.size());
+	}
+	calcExpAltarForHero();
+}
+
+CAltarCreatures::CAltarCreatures(const IMarket * market, const CGHeroInstance * hero)
+	: CAltar(market, hero)
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);
+
+	labels.emplace_back(std::make_shared<CLabel>(155, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW,
+		boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->getNameTranslated())));
+	labels.emplace_back(std::make_shared<CLabel>(450, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[479]));
+	texts.emplace_back(std::make_unique<CTextBox>(CGI->generaltexth->allTexts[480], Rect(320, 56, 256, 40), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW));
+	lSubtitle = std::make_shared<CLabel>(180, 503, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
+	rSubtitle = std::make_shared<CLabel>(426, 503, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
+
+	unitsSlider = std::make_shared<CSlider>(Point(231, 481), 137, std::bind(&CAltarCreatures::onUnitsSliderMoved, this, _1), 0, 0, 0, Orientation::HORIZONTAL);
+	maxUnits = std::make_shared<CButton>(Point(147, 520), AnimationPath::builtin("IRCBTNS.DEF"), CGI->generaltexth->zelp[578], std::bind(&CSlider::scrollToMax, unitsSlider));
+
+	unitsOnAltar.resize(GameConstants::ARMY_SIZE, 0);
+	expPerUnit.resize(GameConstants::ARMY_SIZE, 0);
+	sacrificeAllButton = std::make_shared<CButton>(
+		Point(393, 520), AnimationPath::builtin("ALTARMY.DEF"), CGI->generaltexth->zelp[579], std::bind(&CAltar::sacrificeAll, this));
+
+	// Creating slots for hero creatures
+	for(int slotIdx = 0; slotIdx < GameConstants::ARMY_SIZE; slotIdx++)
+	{
+		CreatureID creatureId = CreatureID::NONE;
+		if(const auto & creature = hero->getCreature(SlotID(slotIdx)))
+			creatureId = creature->getId();
+		else
+			continue;
+
+		auto heroSlot = std::make_shared<CTradeableItem>(posSlotsHero[slotIdx], EType::CREATURE, creatureId.num, true, slotIdx);
+		heroSlot->clickPressedCallback = [this](std::shared_ptr<CTradeableItem> altarSlot) -> void
+			{
+				onSlotClickPressed(altarSlot, items[0], hLeft, hRight);
+			};
+		heroSlot->subtitle = std::to_string(hero->getStackCount(SlotID(slotIdx)));
+		items[1].emplace_back(heroSlot);
+	}
+
+	// Creating slots for creatures on altar
+	assert(items[1].size() <= posSlotsAltar.size());
+	for(const auto & heroSlot : items[1])
+	{
+		auto altarSlot = std::make_shared<CTradeableItem>(posSlotsAltar[heroSlot->serial], EType::CREATURE_PLACEHOLDER, heroSlot->id, false, heroSlot->serial);
+		altarSlot->pos.w = heroSlot->pos.w; altarSlot->pos.h = heroSlot->pos.h;
+		altarSlot->clickPressedCallback = [this](std::shared_ptr<CTradeableItem> altarSlot) -> void
+			{
+				onSlotClickPressed(altarSlot, items[1], hRight, hLeft);
+			};
+		items[0].emplace_back(altarSlot);
+	}
+
+	readExpValues();
+	calcExpAltarForHero();
+	deselect();
+};
+
+void CAltarCreatures::readExpValues()
+{
+	int dump;
+	for(auto heroSlot : items[1])
+	{
+		if(heroSlot->id >= 0)
+			market->getOffer(heroSlot->id, 0, dump, expPerUnit[heroSlot->serial], EMarketMode::CREATURE_EXP);
+	}
+}
+
+void CAltarCreatures::updateControls()
+{
+	int sliderAmount = 0;
+	if(hLeft)
+	{
+		std::optional<SlotID> lastSlot;
+		for(auto slot = SlotID(0); slot.num < GameConstants::ARMY_SIZE; slot++)
+		{
+			if(hero->getStackCount(slot) > unitsOnAltar[slot.num])
+			{
+				if(lastSlot.has_value())
+				{
+					lastSlot = std::nullopt;
+					break;
+				}
+				else
+				{
+					lastSlot = slot;
+				}
+			}
+		}
+		sliderAmount = hero->getStackCount(SlotID(hLeft->serial));
+		if(lastSlot.has_value() && lastSlot.value() == SlotID(hLeft->serial))
+			sliderAmount--;
+	}
+	unitsSlider->setAmount(sliderAmount);
+	unitsSlider->block(!unitsSlider->getAmount());
+	if(hLeft)
+		unitsSlider->scrollTo(unitsOnAltar[hLeft->serial]);
+	maxUnits->block(unitsSlider->getAmount() == 0);
+}
+
+void CAltarCreatures::updateSubtitlesForSelected()
+{
+	if(hLeft)
+		lSubtitle->setText(std::to_string(unitsSlider->getValue()));
+	else
+		lSubtitle->setText("");
+	if(hRight)
+		rSubtitle->setText(hRight->subtitle);
+	else
+		rSubtitle->setText("");
+}
+
+void CAltarCreatures::updateGarrison()
+{
+	std::set<std::shared_ptr<CTradeableItem>> empty;
+	getEmptySlots(empty);
+	removeItems(empty);
+	readExpValues();
+	for(auto & heroSlot : items[1])
+		heroSlot->subtitle = std::to_string(hero->getStackCount(SlotID(heroSlot->serial)));
+}
+
+void CAltarCreatures::deselect()
+{
+	CAltar::deselect();
+	unitsSlider->block(true);
+	maxUnits->block(true);
+	updateSubtitlesForSelected();
+}
+
+TExpType CAltarCreatures::calcExpAltarForHero()
+{
+	TExpType expOnAltar(0);
+	auto oneUnitExp = expPerUnit.begin();
+	for(const auto units : unitsOnAltar)
+		expOnAltar += *oneUnitExp++ * units;
+	auto resultExp = hero->calculateXp(expOnAltar);
+	expForHero->setText(std::to_string(resultExp));
+	return resultExp;
+}
+
+void CAltarCreatures::makeDeal()
+{
+	deselect();
+	unitsSlider->scrollTo(0);
+	expForHero->setText(std::to_string(0));
+
+	std::vector<ui32> ids;
+	std::vector<ui32> toSacrifice;
+
+	for(int i = 0; i < unitsOnAltar.size(); i++)
+	{
+		if(unitsOnAltar[i])
+		{
+			ids.push_back(i);
+			toSacrifice.push_back(unitsOnAltar[i]);
+		}
+	}
+
+	LOCPLINT->cb->trade(market, EMarketMode::CREATURE_EXP, ids, {}, toSacrifice, hero);
+
+	for(int & units : unitsOnAltar)
+		units = 0;
+
+	for(auto heroSlot : items[0])
+	{
+		heroSlot->setType(CREATURE_PLACEHOLDER);
+		heroSlot->subtitle = "";
+	}
+}
+
+void CAltarCreatures::sacrificeAll()
+{
+	std::optional<SlotID> lastSlot;
+	for(auto heroSlot : items[1])
+	{
+		auto stackCount = hero->getStackCount(SlotID(heroSlot->serial));
+		if(stackCount > unitsOnAltar[heroSlot->serial])
+		{
+			if(!lastSlot.has_value())
+				lastSlot = SlotID(heroSlot->serial);
+			unitsOnAltar[heroSlot->serial] = stackCount;
+		}
+	}
+	assert(lastSlot.has_value());
+	unitsOnAltar[lastSlot.value().num]--;
+
+	if(hRight)
+		unitsSlider->scrollTo(unitsOnAltar[hRight->serial]);
+	for(auto altarSlot : items[0])
+		updateAltarSlot(altarSlot);
+	updateSubtitlesForSelected();
+
+	deal->block(calcExpAltarForHero() == 0);
+}
+
+void CAltarCreatures::updateAltarSlot(std::shared_ptr<CTradeableItem> slot)
+{
+	auto units = unitsOnAltar[slot->serial];
+	slot->setType(units > 0 ? CREATURE : CREATURE_PLACEHOLDER);
+	slot->subtitle = units > 0 ?
+		boost::str(boost::format(CGI->generaltexth->allTexts[122]) % std::to_string(hero->calculateXp(units * expPerUnit[slot->serial]))) : "";
+}
+
+void CAltarCreatures::onUnitsSliderMoved(int newVal)
+{
+	if(hLeft)
+		unitsOnAltar[hLeft->serial] = newVal;
+	if(hRight)
+		updateAltarSlot(hRight);
+	deal->block(calcExpAltarForHero() == 0);
+	updateControls();
+	updateSubtitlesForSelected();
+}
+
+void CAltarCreatures::onSlotClickPressed(std::shared_ptr<CTradeableItem> altarSlot,
+	std::vector<std::shared_ptr<CTradeableItem>> & oppositeSlots,
+	std::shared_ptr<CTradeableItem> & hCurSide, std::shared_ptr<CTradeableItem> & hOppSide)
+{
+	std::shared_ptr<CTradeableItem> oppositeSlot;
+	for(const auto & slot : oppositeSlots)
+		if(slot->serial == altarSlot->serial)
+		{
+			oppositeSlot = slot;
+			break;
+		}
+
+	if(hCurSide != altarSlot && oppositeSlot)
+	{
+		hCurSide = altarSlot;
+		hOppSide = oppositeSlot;
+		updateControls();
+		updateSubtitlesForSelected();
+		redraw();
+	}
+}

+ 103 - 0
client/widgets/CAltar.h

@@ -0,0 +1,103 @@
+/*
+ * CAltar.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../widgets/CArtifactsOfHeroAltar.h"
+#include "../widgets/CTradeBase.h"
+
+class CSlider;
+
+class CAltar : public CTradeBase, public CIntObject
+{
+public:
+	std::shared_ptr<CLabel> expToLevel;
+	std::shared_ptr<CLabel> expForHero;
+	std::shared_ptr<CButton> sacrificeAllButton;
+
+	CAltar(const IMarket * market, const CGHeroInstance * hero);
+	virtual ~CAltar() = default;
+	virtual void sacrificeAll() = 0;
+	virtual void deselect();
+	virtual TExpType calcExpAltarForHero() = 0;
+};
+
+class CAltarArtifacts : public CAltar
+{
+public:
+	CAltarArtifacts(const IMarket * market, const CGHeroInstance * hero);
+	TExpType calcExpAltarForHero() override;
+	void makeDeal() override;
+	void sacrificeAll() override;
+	void sacrificeBackpack();
+	void setSelectedArtifact(const CArtifactInstance * art);
+	void moveArtToAltar(std::shared_ptr<CTradeableItem>, const CArtifactInstance * art);
+	std::shared_ptr<CArtifactsOfHeroAltar> getAOHset() const;
+
+private:
+	std::shared_ptr<CArtPlace> selectedArt;
+	std::shared_ptr<CLabel> selectedCost;
+	std::shared_ptr<CButton> sacrificeBackpackButton;
+	std::shared_ptr<CArtifactsOfHeroAltar> arts;
+
+	const std::vector<Point> posSlotsAltar =
+	{
+		Point(317, 53), Point(371, 53), Point(425, 53),
+		Point(479, 53), Point(533, 53), Point(317, 123),
+		Point(371, 123), Point(425, 123), Point(479, 123),
+		Point(533, 123), Point(317, 193), Point(371, 193),
+		Point(425, 193), Point(479, 193), Point(533, 193),
+		Point(317, 263), Point(371, 263), Point(425, 263),
+		Point(479, 263), Point(533, 263), Point(398, 333),
+		Point(452, 333)
+	};
+
+	bool putArtOnAltar(std::shared_ptr<CTradeableItem> altarSlot, const CArtifactInstance * art);
+	void onSlotClickPressed(std::shared_ptr<CTradeableItem> altarSlot);
+};
+
+class CAltarCreatures : public CAltar
+{
+public:
+	CAltarCreatures(const IMarket * market, const CGHeroInstance * hero);
+	void updateGarrison();
+	void deselect() override;
+	TExpType calcExpAltarForHero() override;
+	void makeDeal() override;
+	void sacrificeAll() override;
+	void updateAltarSlot(std::shared_ptr<CTradeableItem> slot);
+
+private:
+	std::shared_ptr<CButton> maxUnits;
+	std::shared_ptr<CSlider> unitsSlider;
+	std::vector<int> unitsOnAltar;
+	std::vector<int> expPerUnit;
+	std::shared_ptr<CLabel> lSubtitle, rSubtitle;
+
+	const std::vector<Point> posSlotsAltar =
+	{
+		Point(334, 110), Point(417, 110), Point(500, 110),
+		Point(334, 208), Point(417, 208), Point(500, 208),
+		Point(417, 306)
+	};
+	const std::vector<Point> posSlotsHero =
+	{
+		Point(45, 110), Point(128, 110), Point(211, 110),
+		Point(45, 208), Point(128, 208), Point(211, 208),
+		Point(128, 306)
+	};
+
+	void readExpValues();
+	void updateControls();
+	void updateSubtitlesForSelected();
+	void onUnitsSliderMoved(int newVal);
+	void onSlotClickPressed(std::shared_ptr<CTradeableItem> altarSlot,
+		std::vector<std::shared_ptr<CTradeableItem>> & oppositeSlots,
+		std::shared_ptr<CTradeableItem> & hCurSide, std::shared_ptr<CTradeableItem> & hOppSide);
+};

+ 32 - 70
client/widgets/CArtifactHolder.cpp

@@ -75,12 +75,26 @@ void CArtPlace::setInternals(const CArtifactInstance * artInst)
 	text = artInst->getDescription();
 }
 
-CArtPlace::CArtPlace(Point position, const CArtifactInstance * Art) 
-	: ourArt(Art)
+CArtPlace::CArtPlace(Point position, const CArtifactInstance * art) 
+	: ourArt(art)
+	, locked(false)
 {
-	image = nullptr;
 	pos += position;
 	pos.w = pos.h = 44;
+
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
+
+	imageIndex = 0;
+	if(locked)
+		imageIndex = ArtifactID::ART_LOCK;
+	else if(ourArt)
+		imageIndex = ourArt->artType->getIconIndex();
+
+	image = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), imageIndex);
+	image->disable();
+
+	selection = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), ArtifactID::ART_SELECTION, 0, -1, -1);
+	selection->visible = false;
 }
 
 const CArtifactInstance * CArtPlace::getArt()
@@ -88,26 +102,12 @@ const CArtifactInstance * CArtPlace::getArt()
 	return ourArt;
 }
 
-CCommanderArtPlace::CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * Art)
-	: CArtPlace(position, Art),
+CCommanderArtPlace::CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * art)
+	: CArtPlace(position, art),
 	commanderOwner(commanderOwner),
 	commanderSlotID(artSlot.num)
 {
-	createImage();
-	setArtifact(Art);
-}
-
-void CCommanderArtPlace::createImage()
-{
-	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
-
-	imageIndex = 0;
-	if(ourArt)
-		imageIndex = ourArt->artType->getIconIndex();
-
-	image = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), imageIndex);
-	if(!ourArt)
-		image->disable();
+	setArtifact(art);
 }
 
 void CCommanderArtPlace::returnArtToHeroCallback()
@@ -145,20 +145,12 @@ void CCommanderArtPlace::showPopupWindow(const Point & cursorPosition)
 		CArtPlace::showPopupWindow(cursorPosition);
 }
 
-void CCommanderArtPlace::setArtifact(const CArtifactInstance * art)
-{
-	setInternals(art);
-}
-
-CHeroArtPlace::CHeroArtPlace(Point position, const CArtifactInstance * Art)
-	: CArtPlace(position, Art),
-	locked(false),
-	marked(false)
+CHeroArtPlace::CHeroArtPlace(Point position, const CArtifactInstance * art)
+	: CArtPlace(position, art)
 {
-	createImage();
 }
 
-void CHeroArtPlace::lockSlot(bool on)
+void CArtPlace::lockSlot(bool on)
 {
 	if(locked == on)
 		return;
@@ -173,26 +165,19 @@ void CHeroArtPlace::lockSlot(bool on)
 		image->setFrame(0);
 }
 
-bool CHeroArtPlace::isLocked()
+bool CArtPlace::isLocked() const
 {
 	return locked;
 }
 
-void CHeroArtPlace::selectSlot(bool on)
+void CArtPlace::selectSlot(bool on)
 {
-	if(marked == on)
-		return;
-
-	marked = on;
-	if(on)
-		selection->enable();
-	else
-		selection->disable();
+	selection->visible = on;
 }
 
-bool CHeroArtPlace::isMarked() const
+bool CArtPlace::isSelected() const
 {
-	return marked;
+	return selection->visible;
 }
 
 void CHeroArtPlace::clickPressed(const Point & cursorPosition)
@@ -207,18 +192,13 @@ void CHeroArtPlace::showPopupWindow(const Point & cursorPosition)
 		showPopupCallback(*this);
 }
 
-void CHeroArtPlace::showAll(Canvas & to)
+void CArtPlace::showAll(Canvas & to)
 {
-	if(ourArt)
-	{
-		CIntObject::showAll(to);
-	}
-
-	if(marked && isActive())
-		to.drawBorder(pos, Colors::BRIGHT_YELLOW);
+	CIntObject::showAll(to);
+	selection->showAll(to);
 }
 
-void CHeroArtPlace::setArtifact(const CArtifactInstance * art)
+void CArtPlace::setArtifact(const CArtifactInstance * art)
 {
 	setInternals(art);
 	if(art)
@@ -253,24 +233,6 @@ void CHeroArtPlace::addCombinedArtInfo(std::map<const CArtifact*, int> & arts)
 	}
 }
 
-void CHeroArtPlace::createImage()
-{
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-
-	si32 imageIndex = 0;
-
-	if(locked)
-		imageIndex = ArtifactID::ART_LOCK;
-	else if(ourArt)
-		imageIndex = ourArt->artType->getIconIndex();
-
-	image = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), imageIndex);
-	image->disable();
-
-	selection = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), ArtifactID::ART_SELECTION);
-	selection->disable();
-}
-
 bool ArtifactUtilsClient::askToAssemble(const CGHeroInstance * hero, const ArtifactPosition & slot)
 {
 	assert(hero);

+ 14 - 25
client/widgets/CArtifactHolder.h

@@ -19,7 +19,6 @@ class CArtifactSet;
 VCMI_LIB_NAMESPACE_END
 
 class CAnimImage;
-class CButton;
 
 class CArtifactHolder
 {
@@ -32,19 +31,24 @@ public:
 
 class CArtPlace : public LRClickableAreaWTextComp
 {
+public:
+	CArtPlace(Point position, const CArtifactInstance * art = nullptr);
+	const CArtifactInstance* getArt();
+	void lockSlot(bool on);
+	bool isLocked() const;
+	void selectSlot(bool on);
+	bool isSelected() const;
+	void showAll(Canvas & to) override;
+	void setArtifact(const CArtifactInstance * art);
+
 protected:
 	std::shared_ptr<CAnimImage> image;
 	const CArtifactInstance * ourArt;
 	int imageIndex;
+	std::shared_ptr<CAnimImage> selection;
+	bool locked;
 
 	void setInternals(const CArtifactInstance * artInst);
-	virtual void createImage()=0;
-
-public:
-	CArtPlace(Point position, const CArtifactInstance * Art = nullptr);
-	const CArtifactInstance * getArt();
-
-	virtual void setArtifact(const CArtifactInstance * art)=0;
 };
 
 class CCommanderArtPlace : public CArtPlace
@@ -53,14 +57,12 @@ protected:
 	const CGHeroInstance * commanderOwner;
 	ArtifactPosition commanderSlotID;
 
-	void createImage() override;
 	void returnArtToHeroCallback();
 
 public:
-	CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * Art = nullptr);
+	CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * art = nullptr);
 	void clickPressed(const Point & cursorPosition) override;
 	void showPopupWindow(const Point & cursorPosition) override;
-	void setArtifact(const CArtifactInstance * art) override;
 };
 
 class CHeroArtPlace: public CArtPlace
@@ -72,23 +74,10 @@ public:
 	ClickFunctor leftClickCallback;
 	ClickFunctor showPopupCallback;
 
-	CHeroArtPlace(Point position, const CArtifactInstance * Art = nullptr);
-	void lockSlot(bool on);
-	bool isLocked();
-	void selectSlot(bool on);
-	bool isMarked() const;
+	CHeroArtPlace(Point position, const CArtifactInstance * art = nullptr);
 	void clickPressed(const Point & cursorPosition) override;
 	void showPopupWindow(const Point & cursorPosition) override;
-	void showAll(Canvas & to) override;
-	void setArtifact(const CArtifactInstance * art) override;
 	void addCombinedArtInfo(std::map<const CArtifact*, int> & arts);
-
-protected:
-	std::shared_ptr<CAnimImage> selection;
-	bool locked;
-	bool marked;
-
-	void createImage() override;
 };
 
 namespace ArtifactUtilsClient

+ 7 - 0
client/widgets/CArtifactsOfHeroAltar.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 #include "CArtifactsOfHeroAltar.h"
 
+#include "Buttons.h"
 #include "../CPlayerInterface.h"
 
 #include "../../CCallback.h"
@@ -27,6 +28,12 @@ CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position)
 		position,
 		std::bind(&CArtifactsOfHeroAltar::scrollBackpack, this, _1));
 	pickedArtFromSlot = ArtifactPosition::PRE_FIRST;
+
+	// The backpack is in the altar window above and to the right
+	for(auto & slot : backpack)
+		slot->moveBy(Point(2, -1));
+	leftBackpackRoll->moveBy(Point(2, -1));
+	rightBackpackRoll->moveBy(Point(2, -1));
 };
 
 CArtifactsOfHeroAltar::~CArtifactsOfHeroAltar()

+ 8 - 6
client/widgets/CArtifactsOfHeroBase.h

@@ -11,6 +11,8 @@
 
 #include "CArtifactHolder.h"
 
+class CButton;
+
 class CArtifactsOfHeroBase : public CIntObject
 {
 protected:
@@ -52,13 +54,13 @@ protected:
 
 	const std::vector<Point> slotPos =
 	{
-		Point(509,30),  Point(567,240), Point(509,80),  //0-2
-		Point(383,68),  Point(564,183), Point(509,130), //3-5
-		Point(431,68),  Point(610,183), Point(515,295), //6-8
-		Point(383,143), Point(399,194), Point(415,245), //9-11
-		Point(431,296), Point(564,30),  Point(610,30), //12-14
+		Point(509,30),  Point(568,242), Point(509,80),  //0-2
+		Point(383,69),  Point(562,184), Point(509,131), //3-5
+		Point(431,69),  Point(610,184), Point(515,295), //6-8
+		Point(383,143), Point(399,193), Point(415,244), //9-11
+		Point(431,295), Point(564,30),  Point(610,30), //12-14
 		Point(610,76),  Point(610,122), Point(610,310), //15-17
-		Point(381,296) //18
+		Point(381,295) //18
 	};
 
 	virtual void init(CHeroArtPlace::ClickFunctor lClickCallback, CHeroArtPlace::ClickFunctor showPopupCallback,

+ 1 - 1
client/widgets/CArtifactsOfHeroMarket.cpp

@@ -30,7 +30,7 @@ void CArtifactsOfHeroMarket::scrollBackpack(int offset)
 	{
 		for(auto & artPlace : backpack)
 		{
-			if(artPlace->isMarked())
+			if(artPlace->isSelected())
 			{
 				selectArtCallback(artPlace.get());
 				break;

+ 285 - 0
client/widgets/CTradeBase.cpp

@@ -0,0 +1,285 @@
+/*
+ * CTradeBase.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CTradeBase.h"
+
+#include "../gui/CGuiHandler.h"
+#include "../render/Canvas.h"
+#include "../widgets/TextControls.h"
+#include "../windows/InfoWindows.h"
+
+#include "../CGameInfo.h"
+
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+CTradeBase::CTradeableItem::CTradeableItem(Point pos, EType Type, int ID, bool Left, int Serial)
+	: CIntObject(LCLICK | HOVER | SHOW_POPUP, pos)
+	, type(EType(-1)) // set to invalid, will be corrected in setType
+	, id(ID)
+	, serial(Serial)
+	, left(Left)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
+	downSelection = false;
+	hlp = nullptr;
+	setType(Type);
+	if(image)
+	{
+		this->pos.w = image->pos.w;
+		this->pos.h = image->pos.h;
+	}
+}
+
+void CTradeBase::CTradeableItem::setType(EType newType)
+{
+	if(type != newType)
+	{
+		OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);
+		type = newType;
+
+		if(getIndex() < 0)
+		{
+			image = std::make_shared<CAnimImage>(getFilename(), 0);
+			image->disable();
+		}
+		else
+		{
+			image = std::make_shared<CAnimImage>(getFilename(), getIndex());
+		}
+	}
+}
+
+void CTradeBase::CTradeableItem::setID(int newID)
+{
+	if(id != newID)
+	{
+		id = newID;
+		if(image)
+		{
+			int index = getIndex();
+			if(index < 0)
+				image->disable();
+			else
+			{
+				image->enable();
+				image->setFrame(index);
+			}
+		}
+	}
+}
+
+AnimationPath CTradeBase::CTradeableItem::getFilename()
+{
+	switch(type)
+	{
+	case RESOURCE:
+		return AnimationPath::builtin("RESOURCE");
+	case PLAYER:
+		return AnimationPath::builtin("CREST58");
+	case ARTIFACT_TYPE:
+	case ARTIFACT_PLACEHOLDER:
+	case ARTIFACT_INSTANCE:
+		return AnimationPath::builtin("artifact");
+	case CREATURE:
+		return AnimationPath::builtin("TWCRPORT");
+	default:
+		return {};
+	}
+}
+
+int CTradeBase::CTradeableItem::getIndex()
+{
+	if(id < 0)
+		return -1;
+
+	switch(type)
+	{
+	case RESOURCE:
+	case PLAYER:
+		return id;
+	case ARTIFACT_TYPE:
+	case ARTIFACT_INSTANCE:
+	case ARTIFACT_PLACEHOLDER:
+		return CGI->artifacts()->getByIndex(id)->getIconIndex();
+	case CREATURE:
+		return CGI->creatures()->getByIndex(id)->getIconIndex();
+	default:
+		return -1;
+	}
+}
+
+void CTradeBase::CTradeableItem::showAll(Canvas & to)
+{
+	Point posToBitmap;
+	Point posToSubCenter;
+
+	switch (type)
+	{
+	case RESOURCE:
+		posToBitmap = Point(19, 9);
+		posToSubCenter = Point(36, 59);
+		break;
+	case CREATURE_PLACEHOLDER:
+	case CREATURE:
+		posToSubCenter = Point(29, 77);
+		break;
+	case PLAYER:
+		posToSubCenter = Point(31, 76);
+		break;
+	case ARTIFACT_PLACEHOLDER:
+	case ARTIFACT_INSTANCE:
+		posToSubCenter = Point(19, 54);
+		if (downSelection)
+			posToSubCenter.y += 8;
+		break;
+	case ARTIFACT_TYPE:
+		posToSubCenter = Point(19, 58);
+		break;
+	}
+
+	if(image)
+	{
+		image->moveTo(pos.topLeft() + posToBitmap);
+		CIntObject::showAll(to);
+	}
+
+	to.drawText(pos.topLeft() + posToSubCenter, FONT_SMALL, Colors::WHITE, ETextAlignment::CENTER, subtitle);
+}
+
+void CTradeBase::CTradeableItem::clickPressed(const Point& cursorPosition)
+{
+	if(clickPressedCallback)
+		clickPressedCallback(shared_from_this());
+}
+
+void CTradeBase::CTradeableItem::showAllAt(const Point& dstPos, const std::string& customSub, Canvas& to)
+{
+	Rect oldPos = pos;
+	std::string oldSub = subtitle;
+	downSelection = true;
+
+	moveTo(dstPos);
+	subtitle = customSub;
+	showAll(to);
+
+	downSelection = false;
+	moveTo(oldPos.topLeft());
+	subtitle = oldSub;
+}
+
+void CTradeBase::CTradeableItem::hover(bool on)
+{
+	if(!on)
+	{
+		GH.statusbar()->clear();
+		return;
+	}
+
+	switch(type)
+	{
+	case CREATURE:
+	case CREATURE_PLACEHOLDER:
+		GH.statusbar()->write(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->getNamePluralTranslated()));
+		break;
+	case ARTIFACT_PLACEHOLDER:
+		if(id < 0)
+			GH.statusbar()->write(CGI->generaltexth->zelp[582].first);
+		else
+			GH.statusbar()->write(CGI->artifacts()->getByIndex(id)->getNameTranslated());
+		break;
+	}
+}
+
+void CTradeBase::CTradeableItem::showPopupWindow(const Point& cursorPosition)
+{
+	switch(type)
+	{
+	case CREATURE:
+	case CREATURE_PLACEHOLDER:
+		break;
+	case ARTIFACT_TYPE:
+	case ARTIFACT_PLACEHOLDER:
+		//TODO: it's would be better for market to contain actual CArtifactInstance and not just ids of certain artifact type so we can use getEffectiveDescription.
+		if (id >= 0)
+			CRClickPopup::createAndPush(CGI->artifacts()->getByIndex(id)->getDescriptionTranslated());
+		break;
+	}
+}
+
+std::string CTradeBase::CTradeableItem::getName(int number) const
+{
+	switch(type)
+	{
+	case PLAYER:
+		return CGI->generaltexth->capColors[id];
+	case RESOURCE:
+		return CGI->generaltexth->restypes[id];
+	case CREATURE:
+		if (number == 1)
+			return CGI->creh->objects[id]->getNameSingularTranslated();
+		else
+			return CGI->creh->objects[id]->getNamePluralTranslated();
+	case ARTIFACT_TYPE:
+	case ARTIFACT_INSTANCE:
+		return CGI->artifacts()->getByIndex(id)->getNameTranslated();
+	}
+	logGlobal->error("Invalid trade item type: %d", (int)type);
+	return "";
+}
+
+const CArtifactInstance* CTradeBase::CTradeableItem::getArtInstance() const
+{
+	switch(type)
+	{
+	case ARTIFACT_PLACEHOLDER:
+	case ARTIFACT_INSTANCE:
+		return hlp;
+	default:
+		return nullptr;
+	}
+}
+
+void CTradeBase::CTradeableItem::setArtInstance(const CArtifactInstance * art)
+{
+	assert(type == ARTIFACT_PLACEHOLDER || type == ARTIFACT_INSTANCE);
+	hlp = art;
+	if(art)
+		setID(art->artType->getId());
+	else
+		setID(-1);
+}
+
+CTradeBase::CTradeBase(const IMarket * market, const CGHeroInstance * hero)
+	: market(market)
+	, hero(hero)
+{
+}
+
+void CTradeBase::removeItems(const std::set<std::shared_ptr<CTradeableItem>> & toRemove)
+{
+	for(auto item : toRemove)
+		removeItem(item);
+}
+
+void CTradeBase::removeItem(std::shared_ptr<CTradeableItem> item)
+{
+	items[item->left] -= item;
+
+	if(hRight == item)
+		hRight.reset();
+}
+
+void CTradeBase::getEmptySlots(std::set<std::shared_ptr<CTradeableItem>> & toRemove)
+{
+	for(auto item : items[1])
+		if(!hero->getStackCount(SlotID(item->serial)))
+			toRemove.insert(item);
+}

+ 88 - 0
client/widgets/CTradeBase.h

@@ -0,0 +1,88 @@
+/*
+ * CTradeBase.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "Images.h"
+
+#include "../../lib/FunctionList.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class IMarket;
+class CGHeroInstance;
+
+VCMI_LIB_NAMESPACE_END
+
+class CButton;
+class CTextBox;
+
+class CTradeBase
+{
+public:
+	enum EType
+	{
+		RESOURCE, PLAYER, ARTIFACT_TYPE, CREATURE, CREATURE_PLACEHOLDER, ARTIFACT_PLACEHOLDER, ARTIFACT_INSTANCE
+	};
+
+	class CTradeableItem : public CIntObject, public std::enable_shared_from_this<CTradeableItem>
+	{
+		std::shared_ptr<CAnimImage> image;
+		AnimationPath getFilename();
+		int getIndex();
+	public:
+		const CArtifactInstance * hlp; //holds ptr to artifact instance id type artifact
+		EType type;
+		int id;
+		const int serial;
+		const bool left;
+		std::string subtitle; //empty if default
+		std::function<void(std::shared_ptr<CTradeableItem> altarSlot)> clickPressedCallback;
+
+		void setType(EType newType);
+		void setID(int newID);
+
+		const CArtifactInstance* getArtInstance() const;
+		void setArtInstance(const CArtifactInstance * art);
+
+		CFunctionList<void()> callback;
+		bool downSelection;
+
+		void showAllAt(const Point & dstPos, const std::string & customSub, Canvas & to);
+
+		void showPopupWindow(const Point & cursorPosition) override;
+		void hover(bool on) override;
+		void showAll(Canvas & to) override;
+		void clickPressed(const Point & cursorPosition) override;
+		std::string getName(int number = -1) const;
+		CTradeableItem(Point pos, EType Type, int ID, bool Left, int Serial);
+	};
+
+	const IMarket * market;
+	const CGHeroInstance * hero;
+
+	//all indexes: 1 = left, 0 = right
+	std::array<std::vector<std::shared_ptr<CTradeableItem>>, 2> items;
+
+	//highlighted items (nullptr if no highlight)
+	std::shared_ptr<CTradeableItem> hLeft;
+	std::shared_ptr<CTradeableItem> hRight;
+	std::shared_ptr<CButton> deal;
+
+	CTradeBase(const IMarket * market, const CGHeroInstance * hero);
+	void removeItems(const std::set<std::shared_ptr<CTradeableItem>> & toRemove);
+	void removeItem(std::shared_ptr<CTradeableItem> item);
+	void getEmptySlots(std::set<std::shared_ptr<CTradeableItem>> & toRemove);
+	virtual void makeDeal() = 0;
+
+protected:
+	std::vector<std::shared_ptr<CLabel>> labels;
+	std::vector<std::shared_ptr<CButton>> buttons;
+	std::vector<std::shared_ptr<CTextBox>> texts;
+};

+ 136 - 0
client/windows/CAltarWindow.cpp

@@ -0,0 +1,136 @@
+/*
+ * CAltarWindow.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "CAltarWindow.h"
+
+#include "../gui/CGuiHandler.h"
+#include "../render/Canvas.h"
+#include "../gui/Shortcut.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/TextControls.h"
+
+#include "../CGameInfo.h"
+
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/CHeroHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+CAltarWindow::CAltarWindow(const IMarket * market, const CGHeroInstance * hero, const std::function<void()> & onWindowClosed, EMarketMode mode)
+	: CWindowObject(PLAYER_COLORED, ImagePath::builtin(mode == EMarketMode::CREATURE_EXP ? "ALTARMON.bmp" : "ALTRART2.bmp"))
+	, hero(hero)
+	, windowClosedCallback(onWindowClosed)
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);
+
+	assert(mode == EMarketMode::ARTIFACT_EXP || mode == EMarketMode::CREATURE_EXP);
+	if(mode == EMarketMode::ARTIFACT_EXP)
+		createAltarArtifacts(market, hero);
+	else if(mode == EMarketMode::CREATURE_EXP)
+		createAltarCreatures(market, hero);
+
+	updateExpToLevel();
+	statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
+}
+
+void CAltarWindow::updateExpToLevel()
+{
+	altar->expToLevel->setText(std::to_string(CGI->heroh->reqExp(CGI->heroh->level(altar->hero->exp) + 1) - altar->hero->exp));
+}
+
+void CAltarWindow::updateGarrison()
+{
+	if(auto altarCreatures = std::static_pointer_cast<CAltarCreatures>(altar))
+		altarCreatures->updateGarrison();
+}
+
+const CGHeroInstance * CAltarWindow::getHero() const
+{
+	return hero;
+}
+
+void CAltarWindow::close()
+{
+	if(windowClosedCallback)
+		windowClosedCallback();
+
+	CWindowObject::close();
+}
+
+void CAltarWindow::createAltarArtifacts(const IMarket * market, const CGHeroInstance * hero)
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);
+
+	background = createBg(ImagePath::builtin("ALTRART2.bmp"), PLAYER_COLORED);
+
+	auto altarArtifacts = std::make_shared<CAltarArtifacts>(market, hero);
+	altar = altarArtifacts;
+	artSets.clear();
+	addSetAndCallbacks(altarArtifacts->getAOHset());
+
+	changeModeButton = std::make_shared<CButton>(Point(516, 421), AnimationPath::builtin("ALTSACC.DEF"),
+		CGI->generaltexth->zelp[572], std::bind(&CAltarWindow::createAltarCreatures, this, market, hero));
+	if(altar->hero->getAlignment() == EAlignment::GOOD)
+		changeModeButton->block(true);
+	quitButton = std::make_shared<CButton>(Point(516, 520), AnimationPath::builtin("IOK6432.DEF"),
+		CGI->generaltexth->zelp[568], std::bind(&CAltarWindow::close, this), EShortcut::GLOBAL_RETURN);
+	altar->setRedrawParent(true);
+	redraw();
+}
+
+void CAltarWindow::createAltarCreatures(const IMarket * market, const CGHeroInstance * hero)
+{
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);
+
+	background = createBg(ImagePath::builtin("ALTARMON.bmp"), PLAYER_COLORED);
+
+	altar = std::make_shared<CAltarCreatures>(market, hero);
+
+	changeModeButton = std::make_shared<CButton>(Point(516, 421), AnimationPath::builtin("ALTART.DEF"),
+		CGI->generaltexth->zelp[580], std::bind(&CAltarWindow::createAltarArtifacts, this, market, hero));
+	if(altar->hero->getAlignment() == EAlignment::EVIL)
+		changeModeButton->block(true);
+	quitButton = std::make_shared<CButton>(Point(516, 520), AnimationPath::builtin("IOK6432.DEF"),
+		CGI->generaltexth->zelp[568], std::bind(&CAltarWindow::close, this), EShortcut::GLOBAL_RETURN);
+	altar->setRedrawParent(true);
+	redraw();
+}
+
+void CAltarWindow::artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc, bool withRedraw)
+{
+	if(!getState().has_value())
+		return;
+
+	if(auto altarArtifacts = std::static_pointer_cast<CAltarArtifacts>(altar))
+	{
+		if(const auto pickedArt = getPickedArtifact())
+			altarArtifacts->setSelectedArtifact(pickedArt);
+		else
+			altarArtifacts->setSelectedArtifact(nullptr);
+	}
+	CWindowWithArtifacts::artifactMoved(srcLoc, destLoc, withRedraw);
+}
+
+void CAltarWindow::showAll(Canvas & to)
+{
+	// This func is temporary workaround for compliance with CTradeWindow
+	CWindowObject::showAll(to);
+
+	if(altar->hRight)
+	{
+		to.drawBorder(Rect::createAround(altar->hRight->pos, 1), Colors::BRIGHT_YELLOW, 2);
+		altar->hRight->showAllAt(altar->pos.topLeft() + Point(396, 423), "", to);
+	}
+	if(altar->hLeft)
+	{
+		to.drawBorder(Rect::createAround(altar->hLeft->pos, 1), Colors::BRIGHT_YELLOW, 2);
+		altar->hLeft->showAllAt(altar->pos.topLeft() + Point(150, 423), "", to);
+	}
+}

+ 38 - 0
client/windows/CAltarWindow.h

@@ -0,0 +1,38 @@
+/*
+ * CAltarWindow.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../widgets/CAltar.h"
+#include "../widgets/CWindowWithArtifacts.h"
+#include "CWindowObject.h"
+
+class CAltarWindow : public CWindowObject, public CWindowWithArtifacts
+{
+public:
+	CAltarWindow(const IMarket * market, const CGHeroInstance * hero, const std::function<void()> & onWindowClosed, EMarketMode mode);
+	void updateExpToLevel();
+	void updateGarrison();
+	const CGHeroInstance * getHero() const;
+	void close() override;
+
+	void artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc, bool withRedraw) override;
+	void showAll(Canvas & to) override;
+
+private:
+	const CGHeroInstance * hero;
+	std::shared_ptr<CAltar> altar;
+	std::shared_ptr<CButton> changeModeButton;
+	std::shared_ptr<CButton> quitButton;
+	std::function<void()> windowClosedCallback;
+	std::shared_ptr<CGStatusBar> statusBar;
+
+	void createAltarArtifacts(const IMarket * market, const CGHeroInstance * hero);
+	void createAltarCreatures(const IMarket * market, const CGHeroInstance * hero);
+};

+ 85 - 859
client/windows/CTradeWindow.cpp

@@ -12,317 +12,28 @@
 
 #include "../gui/CGuiHandler.h"
 #include "../gui/CursorHandler.h"
-#include "../widgets/Images.h"
 #include "../render/Canvas.h"
-#include "../gui/TextAlignment.h"
 #include "../gui/Shortcut.h"
 #include "../gui/WindowHandler.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/Slider.h"
 #include "../widgets/TextControls.h"
-#include "../windows/InfoWindows.h"
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
 
 #include "../../CCallback.h"
 
-#include "../../lib/VCMI_Lib.h"
-#include "../../lib/CArtHandler.h"
-#include "../../lib/CCreatureHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/CGMarket.h"
-#include "../../lib/networkPacks/ArtifactLocation.h"
-
-CTradeWindow::CTradeableItem::CTradeableItem(Point pos, EType Type, int ID, bool Left, int Serial)
-	: CIntObject(LCLICK | HOVER | SHOW_POPUP, pos),
-	type(EType(-1)),// set to invalid, will be corrected in setType
-	id(ID),
-	serial(Serial),
-	left(Left)
-{
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	downSelection = false;
-	hlp = nullptr;
-	setType(Type);
-}
-
-void CTradeWindow::CTradeableItem::setType(EType newType)
-{
-	if(type != newType)
-	{
-		OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
-		type = newType;
-
-		if(getIndex() < 0)
-		{
-			image = std::make_shared<CAnimImage>(getFilename(), 0);
-			image->disable();
-		}
-		else
-		{
-			image = std::make_shared<CAnimImage>(getFilename(), getIndex());
-		}
-	}
-}
-
-void CTradeWindow::CTradeableItem::setID(int newID)
-{
-	if (id != newID)
-	{
-		id = newID;
-		if (image)
-		{
-			int index = getIndex();
-			if (index < 0)
-				image->disable();
-			else
-			{
-				image->enable();
-				image->setFrame(index);
-			}
-		}
-	}
-}
-
-AnimationPath CTradeWindow::CTradeableItem::getFilename()
-{
-	switch(type)
-	{
-	case RESOURCE:
-		return AnimationPath::builtin("RESOURCE");
-	case PLAYER:
-		return AnimationPath::builtin("CREST58");
-	case ARTIFACT_TYPE:
-	case ARTIFACT_PLACEHOLDER:
-	case ARTIFACT_INSTANCE:
-		return AnimationPath::builtin("artifact");
-	case CREATURE:
-		return AnimationPath::builtin("TWCRPORT");
-	default:
-		return {};
-	}
-}
-
-int CTradeWindow::CTradeableItem::getIndex()
-{
-	if (id < 0)
-		return -1;
-
-	switch(type)
-	{
-	case RESOURCE:
-	case PLAYER:
-		return id;
-	case ARTIFACT_TYPE:
-	case ARTIFACT_INSTANCE:
-	case ARTIFACT_PLACEHOLDER:
-		return CGI->artifacts()->getByIndex(id)->getIconIndex();
-	case CREATURE:
-		return CGI->creatures()->getByIndex(id)->getIconIndex();
-	default:
-		return -1;
-	}
-}
-
-void CTradeWindow::CTradeableItem::showAll(Canvas & to)
-{
-	CTradeWindow *mw = dynamic_cast<CTradeWindow *>(parent);
-	assert(mw);
-
-	Point posToBitmap;
-	Point posToSubCenter;
-
-	switch(type)
-	{
-	case RESOURCE:
-		posToBitmap = Point(19,9);
-		posToSubCenter = Point(36, 59);
-		break;
-	case CREATURE_PLACEHOLDER:
-	case CREATURE:
-		posToSubCenter = Point(29, 76);
-		// Positing of unit count is different in Altar of Sacrifice and Freelancer's Guild
-		if(mw->mode == EMarketMode::CREATURE_EXP && downSelection)
-			posToSubCenter.y += 5;
-		break;
-	case PLAYER:
-		posToSubCenter = Point(31, 76);
-		break;
-	case ARTIFACT_PLACEHOLDER:
-	case ARTIFACT_INSTANCE:
-		posToSubCenter = Point(19, 55);
-		if(downSelection)
-			posToSubCenter.y += 8;
-		break;
-	case ARTIFACT_TYPE:
-		posToSubCenter = Point(19, 58);
-		break;
-	}
-
-	if (image)
-	{
-		image->moveTo(pos.topLeft() + posToBitmap);
-		CIntObject::showAll(to);
-	}
-
-	to.drawText(pos.topLeft() + posToSubCenter, FONT_SMALL, Colors::WHITE, ETextAlignment::CENTER, subtitle);
-}
-
-void CTradeWindow::CTradeableItem::clickPressed(const Point & cursorPosition)
-{
-	CTradeWindow *mw = dynamic_cast<CTradeWindow *>(parent);
-	assert(mw);
-		if(type == ARTIFACT_PLACEHOLDER)
-		{
-			CAltarWindow *aw = static_cast<CAltarWindow *>(mw);
-			const auto pickedArtInst = aw->getPickedArtifact();
-
-			if(pickedArtInst)
-			{
-				aw->arts->pickedArtMoveToAltar(ArtifactPosition::TRANSITION_POS);
-				aw->moveArtToAltar(this->shared_from_this(), pickedArtInst);
-			}
-			else if(const CArtifactInstance *art = getArtInstance())
-			{
-				const auto hero = aw->arts->getHero();
-				const auto slot = hero->getSlotByInstance(art);
-				assert(slot != ArtifactPosition::PRE_FIRST);
-				LOCPLINT->cb->swapArtifacts(ArtifactLocation(hero->id, slot),
-					ArtifactLocation(hero->id, ArtifactPosition::TRANSITION_POS));
-				aw->arts->pickedArtFromSlot = slot;
-				aw->arts->artifactsOnAltar.erase(art);
-				setID(-1);
-				subtitle.clear();
-				aw->deal->block(!aw->arts->artifactsOnAltar.size());
-			}
-
-			aw->calcTotalExp();
-			return;
-		}
-		if(left)
-		{
-			if(mw->hLeft != this->shared_from_this())
-				mw->hLeft = this->shared_from_this();
-			else
-				return;
-		}
-		else
-		{
-			if(mw->hRight != this->shared_from_this())
-				mw->hRight = this->shared_from_this();
-			else
-				return;
-		}
-		mw->selectionChanged(left);
-}
-
-void CTradeWindow::CTradeableItem::showAllAt(const Point &dstPos, const std::string &customSub, Canvas & to)
-{
-	Rect oldPos = pos;
-	std::string oldSub = subtitle;
-	downSelection = true;
-
-	moveTo(dstPos);
-	subtitle = customSub;
-	showAll(to);
-
-	downSelection = false;
-	moveTo(oldPos.topLeft());
-	subtitle = oldSub;
-}
-
-void CTradeWindow::CTradeableItem::hover(bool on)
-{
-	if(!on)
-	{
-		GH.statusbar()->clear();
-		return;
-	}
-
-	switch(type)
-	{
-	case CREATURE:
-	case CREATURE_PLACEHOLDER:
-		GH.statusbar()->write(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->getNamePluralTranslated()));
-		break;
-	case ARTIFACT_PLACEHOLDER:
-		if(id < 0)
-			GH.statusbar()->write(CGI->generaltexth->zelp[582].first);
-		else
-			GH.statusbar()->write(CGI->artifacts()->getByIndex(id)->getNameTranslated());
-		break;
-	}
-}
-
-void CTradeWindow::CTradeableItem::showPopupWindow(const Point & cursorPosition)
-{
-	switch(type)
-	{
-		case CREATURE:
-		case CREATURE_PLACEHOLDER:
-			//GH.statusbar->print(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->namePl));
-			break;
-		case ARTIFACT_TYPE:
-		case ARTIFACT_PLACEHOLDER:
-			//TODO: it's would be better for market to contain actual CArtifactInstance and not just ids of certain artifact type so we can use getEffectiveDescription.
-			if(id >= 0)
-				CRClickPopup::createAndPush(CGI->artifacts()->getByIndex(id)->getDescriptionTranslated());
-			break;
-	}
-}
-
-std::string CTradeWindow::CTradeableItem::getName(int number) const
-{
-	switch(type)
-	{
-	case PLAYER:
-		return CGI->generaltexth->capColors[id];
-	case RESOURCE:
-		return CGI->generaltexth->restypes[id];
-	case CREATURE:
-		if(number == 1)
-			return CGI->creh->objects[id]->getNameSingularTranslated();
-		else
-			return CGI->creh->objects[id]->getNamePluralTranslated();
-	case ARTIFACT_TYPE:
-	case ARTIFACT_INSTANCE:
-		return CGI->artifacts()->getByIndex(id)->getNameTranslated();
-	}
-	logGlobal->error("Invalid trade item type: %d", (int)type);
-	return "";
-}
-
-const CArtifactInstance * CTradeWindow::CTradeableItem::getArtInstance() const
-{
-	switch(type)
-	{
-	case ARTIFACT_PLACEHOLDER:
-	case ARTIFACT_INSTANCE:
-		return hlp;
-	default:
-		return nullptr;
-	}
-}
-
-void CTradeWindow::CTradeableItem::setArtInstance(const CArtifactInstance *art)
-{
-	assert(type == ARTIFACT_PLACEHOLDER || type == ARTIFACT_INSTANCE);
-	hlp = art;
-	if(art)
-		setID(art->artType->getId());
-	else
-		setID(-1);
-}
 
 CTradeWindow::CTradeWindow(const ImagePath & bgName, const IMarket *Market, const CGHeroInstance *Hero, const std::function<void()> & onWindowClosed, EMarketMode Mode):
+	CTradeBase(Market, Hero),
 	CWindowObject(PLAYER_COLORED, bgName),
-	market(Market),
 	onWindowClosed(onWindowClosed),
-	hero(Hero),
 	readyToTrade(false)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
@@ -355,14 +66,6 @@ void CTradeWindow::initTypes()
 		itemsType[1] = ARTIFACT_INSTANCE;
 		itemsType[0] = RESOURCE;
 		break;
-	case EMarketMode::CREATURE_EXP:
-		itemsType[1] = CREATURE;
-		itemsType[0] = CREATURE_PLACEHOLDER;
-		break;
-	case EMarketMode::ARTIFACT_EXP:
-		itemsType[1] = ARTIFACT_TYPE;
-		itemsType[0] = ARTIFACT_PLACEHOLDER;
-		break;
 	}
 }
 
@@ -403,6 +106,26 @@ void CTradeWindow::initItems(bool Left)
 
 			auto item = std::make_shared<CTradeableItem>(pos[j].topLeft(), itemsType[Left], id, Left, j);
 			item->pos = pos[j] + this->pos.topLeft();
+			if(mode != EMarketMode::ARTIFACT_EXP)
+				item->clickPressedCallback = [this](std::shared_ptr<CTradeableItem> altarSlot) -> void
+				{
+					if(altarSlot->left)
+					{
+						if(hLeft != altarSlot)
+							hLeft = altarSlot;
+						else
+							return;
+					}
+					else
+					{
+						if(hRight != altarSlot)
+							hRight = altarSlot;
+						else
+							return;
+					}
+					selectionChanged(altarSlot->left);
+				};
+
 			items[Left].push_back(item);
 		}
 		vstd::clear_pointer(ids);
@@ -414,26 +137,7 @@ std::vector<int> *CTradeWindow::getItemsIds(bool Left)
 {
 	std::vector<int> *ids = nullptr;
 
-	if(mode == EMarketMode::ARTIFACT_EXP)
-		return new std::vector<int>(22, -1);
-
-	if(Left)
-	{
-		switch(itemsType[1])
-		{
-		case CREATURE:
-			ids = new std::vector<int>;
-			for(int i = 0; i < 7; i++)
-			{
-				if(const CCreature *c = hero->getCreature(SlotID(i)))
-					ids->push_back(c->getId());
-				else
-					ids->push_back(-1);
-			}
-			break;
-		}
-	}
-	else
+	if(!Left)
 	{
 		switch(itemsType[0])
 		{
@@ -455,50 +159,68 @@ std::vector<int> *CTradeWindow::getItemsIds(bool Left)
 
 void CTradeWindow::getPositionsFor(std::vector<Rect> &poss, bool Left, EType type) const
 {
-	if(mode == EMarketMode::ARTIFACT_EXP && !Left)
+	//seven boxes:
+	//  X  X  X
+	//  X  X  X
+	//     X
+	int h = 0, w = 0, x = 0, y = 0, dx = 0, dy = 0;
+
+	switch(type)
 	{
-		//22 boxes, 5 in row, last row: two boxes centered
-		int h, w, x, y, dx, dy;
-		h = w = 44;
-		x = 317;
-		y = 53;
-		dx = 54;
-		dy = 70;
-		for (int i = 0; i < 4 ; i++)
-			for (int j = 0; j < 5 ; j++)
-				poss.push_back(Rect(x + dx*j, y + dy*i, w, h));
-
-		poss.push_back(Rect((int)(x + dx * 1.5), (y + dy * 4), w, h));
-		poss.push_back(Rect((int)(x + dx * 2.5), (y + dy * 4), w, h));
+	case RESOURCE:
+		dx = 82;
+		dy = 79;
+		x = 39;
+		y = 180;
+		h = 68;
+		w = 70;
+		break;
+	case PLAYER:
+		dx = 83;
+		dy = 118;
+		h = 64;
+		w = 58;
+		x = 44;
+		y = 83;
+		assert(!Left);
+		break;
+	case CREATURE://45,123
+		x = 45;
+		y = 123;
+		w = 58;
+		h = 64;
+		dx = 83;
+		dy = 98;
+		assert(Left);
+		break;
+	case ARTIFACT_TYPE://45,123
+		x = 340 - 289;
+		y = 180;
+		w = 44;
+		h = 44;
+		dx = 83;
+		dy = 79;
+		break;
 	}
-	else
+	int leftToRightOffset = 289;
+
+	const std::vector<Rect> tmp =
 	{
-		//seven boxes:
-		//  X  X  X
-		//  X  X  X
-		//     X
-		int h, w, x, y, dx, dy;
-		int leftToRightOffset;
-		getBaseForPositions(type, dx, dy, x, y, h, w, !Left, leftToRightOffset);
-
-		const std::vector<Rect> tmp =
-		{
-			Rect(Point(x + 0 * dx, y + 0 * dx), Point(w, h) ),
-			Rect(Point(x + 1 * dx, y + 0 * dx), Point(w, h) ),
-			Rect(Point(x + 2 * dx, y + 0 * dx), Point(w, h) ),
-			Rect(Point(x + 0 * dx, y + 1 * dy), Point(w, h) ),
-			Rect(Point(x + 1 * dx, y + 1 * dy), Point(w, h) ),
-			Rect(Point(x + 2 * dx, y + 1 * dy), Point(w, h) ),
-			Rect(Point(x + 1 * dx, y + 2 * dy), Point(w, h) )
-		};
-
-		vstd::concatenate(poss, tmp);
-
-		if(!Left)
-		{
-			for(Rect &r : poss)
-				r.x += leftToRightOffset;
-		}
+		Rect(Point(x + 0 * dx, y + 0 * dx), Point(w, h) ),
+		Rect(Point(x + 1 * dx, y + 0 * dx), Point(w, h) ),
+		Rect(Point(x + 2 * dx, y + 0 * dx), Point(w, h) ),
+		Rect(Point(x + 0 * dx, y + 1 * dy), Point(w, h) ),
+		Rect(Point(x + 1 * dx, y + 1 * dy), Point(w, h) ),
+		Rect(Point(x + 2 * dx, y + 1 * dy), Point(w, h) ),
+		Rect(Point(x + 1 * dx, y + 2 * dy), Point(w, h) )
+	};
+
+	vstd::concatenate(poss, tmp);
+
+	if(!Left)
+	{
+		for(Rect &r : poss)
+			r.x += leftToRightOffset;
 	}
 }
 
@@ -557,9 +279,9 @@ void CTradeWindow::showAll(Canvas & to)
 	if(readyToTrade)
 	{
 		if(hLeft)
-			hLeft->showAllAt(pos.topLeft() + selectionOffset(true), selectionSubtitle(true), to);
+			hLeft->showAllAt(pos.topLeft() + selectionOffset(true), updateSlotSubtitle(true), to);
 		if(hRight)
-			hRight->showAllAt(pos.topLeft() + selectionOffset(false), selectionSubtitle(false), to);
+			hRight->showAllAt(pos.topLeft() + selectionOffset(false), updateSlotSubtitle(false), to);
 	}
 }
 
@@ -571,30 +293,6 @@ void CTradeWindow::close()
 	CWindowObject::close();
 }
 
-void CTradeWindow::removeItems(const std::set<std::shared_ptr<CTradeableItem>> & toRemove)
-{
-	for(auto item : toRemove)
-		removeItem(item);
-}
-
-void CTradeWindow::removeItem(std::shared_ptr<CTradeableItem> item)
-{
-	items[item->left] -= item;
-
-	if(hRight == item)
-	{
-		hRight.reset();
-		selectionChanged(false);
-	}
-}
-
-void CTradeWindow::getEmptySlots(std::set<std::shared_ptr<CTradeableItem>> & toRemove)
-{
-	for(auto item : items[1])
-		if(!hero->getStackCount(SlotID(item->serial)))
-			toRemove.insert(item);
-}
-
 void CTradeWindow::setMode(EMarketMode Mode)
 {
 	const IMarket *m = market;
@@ -608,7 +306,6 @@ void CTradeWindow::setMode(EMarketMode Mode)
 	{
 	case EMarketMode::CREATURE_EXP:
 	case EMarketMode::ARTIFACT_EXP:
-		GH.windows().createAndPushWindow<CAltarWindow>(m, h, functor, Mode);
 		break;
 	default:
 		GH.windows().createAndPushWindow<CMarketplaceWindow>(m, h, functor, Mode);
@@ -905,7 +602,7 @@ bool CMarketplaceWindow::printButtonFor(EMarketMode M) const
 	}
 }
 
-void CMarketplaceWindow::garrisonChanged()
+void CMarketplaceWindow::updateGarrison()
 {
 	if(mode != EMarketMode::CREATURE_RESOURCE)
 		return;
@@ -935,7 +632,7 @@ void CMarketplaceWindow::artifactsChanged(bool Left)
 	redraw();
 }
 
-std::string CMarketplaceWindow::selectionSubtitle(bool Left) const
+std::string CMarketplaceWindow::updateSlotSubtitle(bool Left) const
 {
 	if(Left)
 	{
@@ -1012,49 +709,6 @@ void CMarketplaceWindow::resourceChanged()
 	initSubs(true);
 }
 
-void CMarketplaceWindow::getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const
-{
-	switch(type)
-	{
-	case RESOURCE:
-		dx = 82;
-		dy = 79;
-		x = 39;
-		y = 180;
-		h = 68;
-		w = 70;
-		break;
-	case PLAYER:
-		dx = 83;
-		dy = 118;
-		h = 64;
-		w = 58;
-		x = 44;
-		y = 83;
-		assert(Right);
-		break;
-	case CREATURE://45,123
-		x = 45;
-		y = 123;
-		w = 58;
-		h = 64;
-		dx = 83;
-		dy = 98;
-		assert(!Right);
-		break;
-	case ARTIFACT_TYPE://45,123
-		x = 340-289;
-		y = 180;
-		w = 44;
-		h = 44;
-		dx = 83;
-		dy = 79;
-		break;
-	}
-
-	leftToRightOffset = 289;
-}
-
 void CMarketplaceWindow::updateTraderText()
 {
 	if(readyToTrade)
@@ -1104,431 +758,3 @@ void CMarketplaceWindow::updateTraderText()
 	}
 	traderText->setText(CGI->generaltexth->allTexts[gnrtxtnr]);
 }
-
-CAltarWindow::CAltarWindow(const IMarket * Market, const CGHeroInstance * Hero, const std::function<void()> & onWindowClosed, EMarketMode Mode)
-	: CTradeWindow(ImagePath::builtin(Mode == EMarketMode::CREATURE_EXP ? "ALTARMON.bmp" : "ALTRART2.bmp"), Market, Hero, onWindowClosed, Mode)
-{
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-
-	if(Mode == EMarketMode::CREATURE_EXP)
-	{
-		//%s's Creatures
-		labels.push_back(std::make_shared<CLabel>(155, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW,
-				   boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->getNameTranslated())));
-
-		//Altar of Sacrifice
-		labels.push_back(std::make_shared<CLabel>(450, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[479]));
-
-		 //To sacrifice creatures, move them from your army on to the Altar and click Sacrifice
-		new CTextBox(CGI->generaltexth->allTexts[480], Rect(320, 56, 256, 40), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW);
-
-		slider = std::make_shared<CSlider>(Point(231, 481), 137, std::bind(&CAltarWindow::sliderMoved, this, _1), 0, 0, 0, Orientation::HORIZONTAL);
-		max = std::make_shared<CButton>(Point(147, 520), AnimationPath::builtin("IRCBTNS.DEF"), CGI->generaltexth->zelp[578], std::bind(&CSlider::scrollToMax, slider));
-
-		sacrificedUnits.resize(GameConstants::ARMY_SIZE, 0);
-		sacrificeAll = std::make_shared<CButton>(Point(393, 520), AnimationPath::builtin("ALTARMY.DEF"), CGI->generaltexth->zelp[579], std::bind(&CAltarWindow::SacrificeAll,this));
-
-		initItems(true);
-		mimicCres();
-	}
-	else
-	{
-		//Sacrifice artifacts for experience
-		labels.push_back(std::make_shared<CLabel>(450, 34, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[477]));
-		//%s's Creatures
-		labels.push_back(std::make_shared<CLabel>(302, 423, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[478]));
-
-		sacrificeAll = std::make_shared<CButton>(Point(393, 520), AnimationPath::builtin("ALTFILL.DEF"), CGI->generaltexth->zelp[571], std::bind(&CAltarWindow::SacrificeAll,this));
-		sacrificeAll->block(hero->artifactsInBackpack.empty() && hero->artifactsWorn.empty());
-		sacrificeBackpack = std::make_shared<CButton>(Point(147, 520), AnimationPath::builtin("ALTEMBK.DEF"), CGI->generaltexth->zelp[570], std::bind(&CAltarWindow::SacrificeBackpack,this));
-		sacrificeBackpack->block(hero->artifactsInBackpack.empty());
-
-		arts = std::make_shared<CArtifactsOfHeroAltar>(Point(-365, -12));
-		arts->setHero(hero);
-		addSetAndCallbacks(arts);
-
-		initItems(true);
-		initItems(false);
-		artIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("ARTIFACT"), 0, 0, 281, 442);
-		artIcon->disable();
-	}
-
-	//Experience needed to reach next level
-	texts.push_back(std::make_shared<CTextBox>(CGI->generaltexth->allTexts[475], Rect(15, 415, 125, 50), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW));
-	//Total experience on the Altar
-	texts.push_back(std::make_shared<CTextBox>(CGI->generaltexth->allTexts[476], Rect(15, 495, 125, 40), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW));
-
-	statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
-
-	ok = std::make_shared<CButton>(Point(516, 520), AnimationPath::builtin("IOK6432.DEF"), CGI->generaltexth->zelp[568], [&](){ close();}, EShortcut::GLOBAL_RETURN);
-
-	deal = std::make_shared<CButton>(Point(269, 520), AnimationPath::builtin("ALTSACR.DEF"), CGI->generaltexth->zelp[585], std::bind(&CAltarWindow::makeDeal,this));
-
-	if(Mode == EMarketMode::CREATURE_EXP)
-	{
-		auto changeMode = std::make_shared<CButton>(Point(516, 421), AnimationPath::builtin("ALTART.DEF"), CGI->generaltexth->zelp[580], std::bind(&CTradeWindow::setMode,this, EMarketMode::ARTIFACT_EXP));
-		if(Hero->getAlignment() == ::EAlignment::EVIL)
-			changeMode->block(true);
-		buttons.push_back(changeMode);
-	}
-	else if(Mode == EMarketMode::ARTIFACT_EXP)
-	{
-		auto changeMode = std::make_shared<CButton>(Point(516, 421), AnimationPath::builtin("ALTSACC.DEF"), CGI->generaltexth->zelp[572], std::bind(&CTradeWindow::setMode,this, EMarketMode::CREATURE_EXP));
-		if(Hero->getAlignment() == ::EAlignment::GOOD)
-			changeMode->block(true);
-		buttons.push_back(changeMode);
-	}
-
-	expPerUnit.resize(GameConstants::ARMY_SIZE, 0);
-	getExpValues();
-
-	expToLevel = std::make_shared<CLabel>(73, 475, FONT_SMALL, ETextAlignment::CENTER);
-	expOnAltar = std::make_shared<CLabel>(73, 543, FONT_SMALL, ETextAlignment::CENTER);
-
-	setExpToLevel();
-	calcTotalExp();
-	blockTrade();
-}
-
-CAltarWindow::~CAltarWindow() = default;
-
-void CAltarWindow::getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const
-{
-	leftToRightOffset = 289;
-	x = 45;
-	y = 110;
-	w = 58;
-	h = 64;
-	dx = 83;
-	dy = 98;
-}
-
-void CAltarWindow::sliderMoved(int to)
-{
-	if(hLeft)
-		sacrificedUnits[hLeft->serial] = to;
-	if(hRight)
-		updateRight(hRight);
-
-	deal->block(!to);
-	calcTotalExp();
-	redraw();
-}
-
-void CAltarWindow::makeDeal()
-{
-	if(mode == EMarketMode::CREATURE_EXP)
-	{
-		blockTrade();
-		slider->scrollTo(0);
-
-		std::vector<ui32> ids;
-		std::vector<ui32> toSacrifice;
-
-		for(int i = 0; i < sacrificedUnits.size(); i++)
-		{
-			if(sacrificedUnits[i])
-			{
-				ids.push_back(i);
-				toSacrifice.push_back(sacrificedUnits[i]);
-			}
-		}
-
-		LOCPLINT->cb->trade(market, mode, ids, {}, toSacrifice, hero);
-
-		for(int& val : sacrificedUnits)
-			val = 0;
-
-		for(auto item : items[0])
-		{
-			item->setType(CREATURE_PLACEHOLDER);
-			item->subtitle = "";
-		}
-	}
-	else
-	{
-		std::vector<ui32> positions;
-		for(const CArtifactInstance * art : arts->artifactsOnAltar)
-		{
-			positions.push_back(hero->getSlotByInstance(art));
-		}
-		std::sort(positions.begin(), positions.end(), std::greater<>());
-
-		LOCPLINT->cb->trade(market, mode, positions, {}, {}, hero);
-		arts->artifactsOnAltar.clear();
-
-		for(auto item : items[0])
-		{
-			item->setID(-1);
-			item->subtitle = "";
-		}
-
-		//arts->scrollBackpack(0);
-		deal->block(true);
-	}
-
-	calcTotalExp();
-}
-
-void CAltarWindow::SacrificeAll()
-{
-	if(mode == EMarketMode::CREATURE_EXP)
-	{
-		bool movedAnything = false;
-		for(auto item : items[1])
-			sacrificedUnits[item->serial] = hero->getStackCount(SlotID(item->serial));
-
-		sacrificedUnits[items[1].front()->serial]--;
-
-		for(auto item : items[0])
-		{
-			updateRight(item);
-			if(item->type == CREATURE)
-				movedAnything = true;
-		}
-
-		deal->block(!movedAnything);
-		calcTotalExp();
-	}
-	else
-	{
-		std::vector<ConstTransitivePtr<CArtifactInstance>> artsForMove;
-		for(const auto& slotInfo : arts->visibleArtSet.artifactsWorn)
-		{
-			if(!slotInfo.second.locked && slotInfo.second.artifact->artType->isTradable())
-				artsForMove.push_back(slotInfo.second.artifact);
-		}
-		for(auto artInst : artsForMove)
-			moveArtToAltar(nullptr, artInst);
-		arts->updateWornSlots();
-		SacrificeBackpack();
-	}
-	redraw();
-}
-
-void CAltarWindow::selectionChanged(bool side)
-{
-	if(mode != EMarketMode::CREATURE_EXP)
-		return;
-
-	int stackCount = 0;
-	for (int i = 0; i < GameConstants::ARMY_SIZE; i++)
-		if(hero->getStackCount(SlotID(i)) > sacrificedUnits[i])
-			stackCount++;
-
-	slider->setAmount(hero->getStackCount(SlotID(hLeft->serial)) - (stackCount == 1));
-	slider->block(!slider->getAmount());
-	slider->scrollTo(sacrificedUnits[hLeft->serial]);
-	max->block(!slider->getAmount());
-	selectOppositeItem(side);
-	readyToTrade = true;
-	redraw();
-}
-
-void CAltarWindow::selectOppositeItem(bool side)
-{
-	bool oppositeSide = !side;
-	int pos = vstd::find_pos(items[side], side ? hLeft : hRight);
-	int oppositePos = vstd::find_pos(items[oppositeSide], oppositeSide ? hLeft : hRight);
-
-	if(pos >= 0 && pos != oppositePos)
-	{
-		if(oppositeSide)
-			hLeft = items[oppositeSide][pos];
-		else
-			hRight = items[oppositeSide][pos];
-
-		selectionChanged(oppositeSide);
-	}
-}
-
-void CAltarWindow::mimicCres()
-{
-	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
-	std::vector<Rect> positions;
-	getPositionsFor(positions, false, CREATURE);
-
-	for(auto item : items[1])
-	{
-		auto hlp = std::make_shared<CTradeableItem>(positions[item->serial].topLeft(), CREATURE_PLACEHOLDER, item->id, false, item->serial);
-		hlp->pos = positions[item->serial] + this->pos.topLeft();
-		items[0].push_back(hlp);
-	}
-}
-
-Point CAltarWindow::selectionOffset(bool Left) const
-{
-	if(Left)
-		return Point(150, 421);
-	else
-		return Point(396, 421);
-}
-
-std::string CAltarWindow::selectionSubtitle(bool Left) const
-{
-	if(Left && slider && hLeft)
-		return std::to_string(slider->getValue());
-	else if(!Left && hRight)
-		return hRight->subtitle;
-	else
-		return "";
-}
-
-void CAltarWindow::artifactsChanged(bool left)
-{
-
-}
-
-void CAltarWindow::garrisonChanged()
-{
-	if(mode != EMarketMode::CREATURE_EXP)
-		return;
-
-	std::set<std::shared_ptr<CTradeableItem>> empty;
-	getEmptySlots(empty);
-
-	removeItems(empty);
-
-	initSubs(true);
-	getExpValues();
-}
-
-void CAltarWindow::getExpValues()
-{
-	int dump;
-	for(auto item : items[1])
-	{
-		if(item->id >= 0)
-			market->getOffer(item->id, 0, dump, expPerUnit[item->serial], EMarketMode::CREATURE_EXP);
-	}
-}
-
-void CAltarWindow::calcTotalExp()
-{
-	int val = 0;
-	if(mode == EMarketMode::CREATURE_EXP)
-	{
-		for (int i = 0; i < sacrificedUnits.size(); i++)
-		{
-			val += expPerUnit[i] * sacrificedUnits[i];
-		}
-	}
-	else
-	{
-		auto artifactsOfHero = std::dynamic_pointer_cast<CArtifactsOfHeroAltar>(arts);
-		for(const CArtifactInstance * art : artifactsOfHero->artifactsOnAltar)
-		{
-			int dmp, valOfArt;
-			market->getOffer(art->artType->getId(), 0, dmp, valOfArt, mode);
-			val += valOfArt; //WAS val += valOfArt * arts->artifactsOnAltar.count(*i);
-		}
-	}
-	val = static_cast<int>(hero->calculateXp(val));
-	expOnAltar->setText(std::to_string(val));
-}
-
-void CAltarWindow::setExpToLevel()
-{
-	expToLevel->setText(std::to_string(CGI->heroh->reqExp(CGI->heroh->level(hero->exp)+1) - hero->exp));
-}
-
-void CAltarWindow::blockTrade()
-{
-	hLeft = hRight = nullptr;
-	readyToTrade = false;
-	if(slider)
-	{
-		slider->block(true);
-		max->block(true);
-	}
-	deal->block(true);
-}
-
-void CAltarWindow::updateRight(std::shared_ptr<CTradeableItem> toUpdate)
-{
-	int val = sacrificedUnits[toUpdate->serial];
-	toUpdate->setType(val ? CREATURE : CREATURE_PLACEHOLDER);
-	toUpdate->subtitle = val ? boost::str(boost::format(CGI->generaltexth->allTexts[122]) % std::to_string(hero->calculateXp(val * expPerUnit[toUpdate->serial]))) : ""; //%s exp
-}
-
-int CAltarWindow::firstFreeSlot()
-{
-	int ret = -1;
-	while(items[0][++ret]->id >= 0  &&  ret + 1 < items[0].size());
-	return items[0][ret]->id == -1 ? ret : -1;
-}
-
-void CAltarWindow::SacrificeBackpack()
-{
-	while(!arts->visibleArtSet.artifactsInBackpack.empty())
-	{
-		if(!putOnAltar(nullptr, arts->visibleArtSet.artifactsInBackpack[0].artifact))
-			break;
-	};
-	calcTotalExp();
-}
-
-void CAltarWindow::artifactPicked()
-{
-	redraw();
-}
-
-void CAltarWindow::showAll(Canvas & to)
-{
-	CTradeWindow::showAll(to);
-	if(mode == EMarketMode::ARTIFACT_EXP && arts)
-	{
-		if(auto pickedArt = arts->getPickedArtifact())
-		{
-			artIcon->setFrame(pickedArt->artType->getIconIndex());
-			artIcon->showAll(to);
-
-			int dmp, val;
-			market->getOffer(pickedArt->getTypeId(), 0, dmp, val, EMarketMode::ARTIFACT_EXP);
-			val = static_cast<int>(hero->calculateXp(val));
-
-			to.drawText(Point(304, 498), FONT_SMALL, Colors::WHITE, ETextAlignment::CENTER, std::to_string(val));
-		}
-	}
-}
-
-bool CAltarWindow::putOnAltar(std::shared_ptr<CTradeableItem> altarSlot, const CArtifactInstance *art)
-{
-	if(!art->artType->isTradable()) //special art
-	{
-		logGlobal->warn("Cannot put special artifact on altar!");
-		return false;
-	}
-
-	if(!altarSlot || altarSlot->id != -1)
-	{
-		int slotIndex = firstFreeSlot();
-		if(slotIndex < 0)
-		{
-			logGlobal->warn("No free slots on altar!");
-			return false;
-		}
-		altarSlot = items[0][slotIndex];
-	}
-
-	int dmp, val;
-	market->getOffer(art->artType->getId(), 0, dmp, val, EMarketMode::ARTIFACT_EXP);
-	val = static_cast<int>(hero->calculateXp(val));
-
-	arts->artifactsOnAltar.insert(art);
-	arts->deleteFromVisible(art);
-	altarSlot->setArtInstance(art);
-	altarSlot->subtitle = std::to_string(val);
-
-	deal->block(false);
-	return true;
-}
-
-void CAltarWindow::moveArtToAltar(std::shared_ptr<CTradeableItem> altarSlot, const CArtifactInstance *art)
-{
-	if(putOnAltar(altarSlot, art))
-	{
-		CCS->curh->dragAndDropCursor(nullptr);
-		arts->unmarkSlots();
-	}
-}

+ 7 - 118
client/windows/CTradeWindow.h

@@ -9,76 +9,21 @@
  */
 #pragma once
 
+#include "../widgets/CTradeBase.h"
 #include "../widgets/CWindowWithArtifacts.h"
 #include "CWindowObject.h"
-#include "../../lib/FunctionList.h"
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-class IMarket;
-
-VCMI_LIB_NAMESPACE_END
 
 class CSlider;
-class CTextBox;
-class CPicture;
 class CGStatusBar;
 
-class CTradeWindow : public CWindowObject, public CWindowWithArtifacts //base for markets and altar of sacrifice
+class CTradeWindow : public CTradeBase, public CWindowObject, public CWindowWithArtifacts //base for markets and altar of sacrifice
 {
 public:
-	enum EType
-	{
-		RESOURCE, PLAYER, ARTIFACT_TYPE, CREATURE, CREATURE_PLACEHOLDER, ARTIFACT_PLACEHOLDER, ARTIFACT_INSTANCE
-	};
-
-	class CTradeableItem : public CIntObject, public std::enable_shared_from_this<CTradeableItem>
-	{
-		std::shared_ptr<CAnimImage> image;
-		AnimationPath getFilename();
-		int getIndex();
-	public:
-		const CArtifactInstance * hlp; //holds ptr to artifact instance id type artifact
-		EType type;
-		int id;
-		const int serial;
-		const bool left;
-		std::string subtitle; //empty if default
-
-		void setType(EType newType);
-		void setID(int newID);
-
-		const CArtifactInstance * getArtInstance() const;
-		void setArtInstance(const CArtifactInstance * art);
-
-		CFunctionList<void()> callback;
-		bool downSelection;
-
-		void showAllAt(const Point & dstPos, const std::string & customSub, Canvas & to);
-
-		void showPopupWindow(const Point & cursorPosition) override;
-		void hover(bool on) override;
-		void showAll(Canvas & to) override;
-		void clickPressed(const Point & cursorPosition) override;
-		std::string getName(int number = -1) const;
-		CTradeableItem(Point pos, EType Type, int ID, bool Left, int Serial);
-	};
-
-	const IMarket * market;
-	const CGHeroInstance * hero;
-
-	//all indexes: 1 = left, 0 = right
-	std::array<std::vector<std::shared_ptr<CTradeableItem>>, 2> items;
-
-	//highlighted items (nullptr if no highlight)
-	std::shared_ptr<CTradeableItem> hLeft;
-	std::shared_ptr<CTradeableItem> hRight;
 	EType itemsType[2];
 
 	EMarketMode mode;
 	std::shared_ptr<CButton> ok;
 	std::shared_ptr<CButton> max;
-	std::shared_ptr<CButton> deal;
 
 	std::shared_ptr<CSlider> slider; //for choosing amount to be exchanged
 	bool readyToTrade;
@@ -93,26 +38,18 @@ public:
 	void initItems(bool Left);
 	std::vector<int> *getItemsIds(bool Left); //nullptr if default
 	void getPositionsFor(std::vector<Rect> &poss, bool Left, EType type) const;
-	void removeItems(const std::set<std::shared_ptr<CTradeableItem>> & toRemove);
-	void removeItem(std::shared_ptr<CTradeableItem> item);
-	void getEmptySlots(std::set<std::shared_ptr<CTradeableItem>> & toRemove);
 	void setMode(EMarketMode Mode); //mode setter
 
 	void artifactSelected(CHeroArtPlace *slot); //used when selling artifacts -> called when user clicked on artifact slot
-
-	virtual void getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const = 0;
 	virtual void selectionChanged(bool side) = 0; //true == left
 	virtual Point selectionOffset(bool Left) const = 0;
-	virtual std::string selectionSubtitle(bool Left) const = 0;
-	virtual void garrisonChanged() = 0;
+	virtual std::string updateSlotSubtitle(bool Left) const = 0;
+	virtual void updateGarrison() = 0;
 	virtual void artifactsChanged(bool left) = 0;
 protected:
 	std::function<void()> onWindowClosed;
 	std::shared_ptr<CGStatusBar> statusBar;
-	std::vector<std::shared_ptr<CLabel>> labels;
 	std::vector<std::shared_ptr<CPicture>> images;
-	std::vector<std::shared_ptr<CButton>> buttons;
-	std::vector<std::shared_ptr<CTextBox>> texts;
 };
 
 class CMarketplaceWindow : public CTradeWindow
@@ -130,64 +67,16 @@ public:
 
 	void setMax();
 	void sliderMoved(int to);
-	void makeDeal();
+	void makeDeal() override;
 	void selectionChanged(bool side) override; //true == left
 	CMarketplaceWindow(const IMarket * Market, const CGHeroInstance * Hero, const std::function<void()> & onWindowClosed, EMarketMode Mode);
 	~CMarketplaceWindow();
 
 	Point selectionOffset(bool Left) const override;
-	std::string selectionSubtitle(bool Left) const override;
+	std::string updateSlotSubtitle(bool Left) const override;
 
-	void garrisonChanged() override; //removes creatures with count 0 from the list (apparently whole stack has been sold)
+	void updateGarrison() override; //removes creatures with count 0 from the list (apparently whole stack has been sold)
 	void artifactsChanged(bool left) override;
 	void resourceChanged();
-
-	void getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const override;
 	void updateTraderText();
 };
-
-class CAltarWindow : public CTradeWindow
-{
-	std::shared_ptr<CAnimImage> artIcon;
-public:
-	std::vector<int> sacrificedUnits; //[slot_nr] -> how many creatures from that slot will be sacrificed
-	std::vector<int> expPerUnit;
-
-	std::shared_ptr<CButton> sacrificeAll;
-	std::shared_ptr<CButton> sacrificeBackpack;
-	std::shared_ptr<CLabel> expToLevel;
-	std::shared_ptr<CLabel> expOnAltar;
-	std::shared_ptr<CArtifactsOfHeroAltar> arts;
-
-	CAltarWindow(const IMarket * Market, const CGHeroInstance * Hero, const std::function<void()> & onWindowClosed, EMarketMode Mode);
-	~CAltarWindow();
-
-	void getExpValues();
-
-	void selectionChanged(bool side) override; //true == left
-	void selectOppositeItem(bool side);
-	void SacrificeAll();
-	void SacrificeBackpack();
-
-	void putOnAltar(int backpackIndex);
-	bool putOnAltar(std::shared_ptr<CTradeableItem> altarSlot, const CArtifactInstance * art);
-	void makeDeal();
-	void showAll(Canvas & to) override;
-
-	void blockTrade();
-	void sliderMoved(int to);
-	void getBaseForPositions(EType type, int &dx, int &dy, int &x, int &y, int &h, int &w, bool Right, int &leftToRightOffset) const override;
-	void mimicCres();
-
-	Point selectionOffset(bool Left) const override;
-	std::string selectionSubtitle(bool Left) const override;
-	void garrisonChanged() override;
-	void artifactsChanged(bool left) override;
-	void calcTotalExp();
-	void setExpToLevel();
-	void updateRight(std::shared_ptr<CTradeableItem> toUpdate);
-
-	void artifactPicked();
-	int firstFreeSlot();
-	void moveArtToAltar(std::shared_ptr<CTradeableItem>, const CArtifactInstance * art);
-};

+ 1 - 2
client/windows/CWindowObject.h

@@ -16,8 +16,6 @@ class CGStatusBar;
 
 class CWindowObject : public WindowBase
 {
-	std::shared_ptr<CPicture> createBg(const ImagePath & imageName, bool playerColored);
-
 	std::vector<std::shared_ptr<CPicture>> shadowParts;
 
 	void setShadow(bool on);
@@ -32,6 +30,7 @@ protected:
 	//To display border
 	void updateShadow();
 	void setBackground(const ImagePath & filename);
+	std::shared_ptr<CPicture> createBg(const ImagePath & imageName, bool playerColored);
 public:
 	enum EOptions
 	{

+ 2 - 2
client/windows/GUIClasses.cpp

@@ -677,9 +677,9 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 		manaValues[leftRight] = std::make_shared<CLabel>(155 + 490 * leftRight, qeLayout ? 66 : 71, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
 	}
 
-	artifs[0] = std::make_shared<CArtifactsOfHeroMain>(Point(-334, 150));
+	artifs[0] = std::make_shared<CArtifactsOfHeroMain>(Point(-334, 151));
 	artifs[0]->setHero(heroInst[0]);
-	artifs[1] = std::make_shared<CArtifactsOfHeroMain>(Point(98, 150));
+	artifs[1] = std::make_shared<CArtifactsOfHeroMain>(Point(98, 151));
 	artifs[1]->setHero(heroInst[1]);
 
 	addSetAndCallbacks(artifs[0]);