浏览代码

Merge remote-tracking branch 'vcmi/develop' into battle_improvements

Ivan Savenko 2 年之前
父节点
当前提交
d3ecd43aba

+ 3 - 0
.gitignore

@@ -60,3 +60,6 @@ CMakeUserPresets.json
 /AI/FuzzyLite.lib
 /deps
 .vs/
+
+# CLion
+.idea/

+ 38 - 0
client/battle/BattleActionsController.cpp

@@ -737,7 +737,30 @@ void BattleActionsController::activateStack()
 {
 	const CStack * s = owner.stacksController->getActiveStack();
 	if(s)
+	{
 		possibleActions = getPossibleActionsForStack(s);
+		std::list<PossiblePlayerBattleAction> actionsToSelect;
+		if(!possibleActions.empty())
+		{
+			switch(possibleActions.front())
+			{
+				case PossiblePlayerBattleAction::SHOOT:
+					actionsToSelect.push_back(possibleActions.front());
+					actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK);
+					break;
+					
+				case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
+					actionsToSelect.push_back(possibleActions.front());
+					actionsToSelect.push_back(PossiblePlayerBattleAction::WALK_AND_ATTACK);
+					break;
+					
+				case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
+					actionsToSelect.push_back(possibleActions.front());
+					break;
+			}
+		}
+		owner.windowObject->setAlternativeActions(actionsToSelect);
+	}
 }
 
 bool BattleActionsController::spellcastingModeActive() const
@@ -751,3 +774,18 @@ SpellID BattleActionsController::selectedSpell() const
 		return SpellID::NONE;
 	return SpellID(spellToCast->actionSubtype);
 }
+
+const std::vector<PossiblePlayerBattleAction> & BattleActionsController::getPossibleActions() const
+{
+	return possibleActions;
+}
+
+void BattleActionsController::removePossibleAction(PossiblePlayerBattleAction action)
+{
+	vstd::erase(possibleActions, action);
+}
+
+void BattleActionsController::pushFrontPossibleAction(PossiblePlayerBattleAction action)
+{
+	possibleActions.insert(possibleActions.begin(), action);
+}

+ 7 - 0
client/battle/BattleActionsController.h

@@ -92,5 +92,12 @@ public:
 
 	/// returns true if UI is currently in target selection mode
 	bool spellcastingModeActive() const;
+	
+	/// methods to work with array of possible actions, needed to control special creatures abilities
+	const std::vector<PossiblePlayerBattleAction> & getPossibleActions() const;
+	void removePossibleAction(PossiblePlayerBattleAction);
+	
+	/// inserts possible action in the beggining in order to prioritize it
+	void pushFrontPossibleAction(PossiblePlayerBattleAction);
 
 };

+ 158 - 41
client/battle/BattleWindow.cpp

@@ -23,6 +23,7 @@
 #include "../gui/Canvas.h"
 #include "../gui/CCursorHandler.h"
 #include "../gui/CGuiHandler.h"
+#include "../gui/CAnimation.h"
 #include "../windows/CSpellWindow.h"
 #include "../widgets/AdventureMapClasses.h"
 #include "../widgets/Buttons.h"
@@ -33,6 +34,7 @@
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CConfigHandler.h"
+#include "../../lib/filesystem/ResourceID.h"
 
 BattleWindow::BattleWindow(BattleInterface & owner):
 	owner(owner)
@@ -42,31 +44,27 @@ BattleWindow::BattleWindow(BattleInterface & owner):
 	pos.h = 600;
 	pos = center();
 
-	// create background for panel/ribbon at the bottom
-	menuTactics = std::make_shared<CPicture>("COPLACBR.BMP", 0, 556);
-	menuBattle = std::make_shared<CPicture>("CBAR.BMP", 0, 556);
-	menuTactics->colorize(owner.curInt->playerID);
-	menuBattle->colorize(owner.curInt->playerID);
-
-	//preparing buttons and console
-	bOptions = std::make_shared<CButton>    (Point(  3,  5 + 556), "icm003.def", CGI->generaltexth->zelp[381], std::bind(&BattleWindow::bOptionsf,this), SDLK_o);
-	bSurrender = std::make_shared<CButton>  (Point( 54,  5 + 556), "icm001.def", CGI->generaltexth->zelp[379], std::bind(&BattleWindow::bSurrenderf,this), SDLK_s);
-	bFlee = std::make_shared<CButton>       (Point(105,  5 + 556), "icm002.def", CGI->generaltexth->zelp[380], std::bind(&BattleWindow::bFleef,this), SDLK_r);
-	bAutofight = std::make_shared<CButton>  (Point(158,  5 + 556), "icm004.def", CGI->generaltexth->zelp[382], std::bind(&BattleWindow::bAutofightf,this), SDLK_a);
-	bSpell = std::make_shared<CButton>      (Point(645,  5 + 556), "icm005.def", CGI->generaltexth->zelp[385], std::bind(&BattleWindow::bSpellf,this), SDLK_c);
-	bWait = std::make_shared<CButton>       (Point(696,  5 + 556), "icm006.def", CGI->generaltexth->zelp[386], std::bind(&BattleWindow::bWaitf,this), SDLK_w);
-	bDefence = std::make_shared<CButton>    (Point(747,  5 + 556), "icm007.def", CGI->generaltexth->zelp[387], std::bind(&BattleWindow::bDefencef,this), SDLK_d);
-	bConsoleUp = std::make_shared<CButton>  (Point(624,  5 + 556), "ComSlide.def", std::make_pair("", ""),     std::bind(&BattleWindow::bConsoleUpf,this), SDLK_UP);
-	bConsoleDown = std::make_shared<CButton>(Point(624, 24 + 556), "ComSlide.def", std::make_pair("", ""),     std::bind(&BattleWindow::bConsoleDownf,this), SDLK_DOWN);
-
-	bDefence->assignedKeys.insert(SDLK_SPACE);
-	bConsoleUp->setImageOrder(0, 1, 0, 0);
-	bConsoleDown->setImageOrder(2, 3, 2, 2);
-
-	btactNext = std::make_shared<CButton>(Point(213, 4 + 556), "icm011.def", std::make_pair("", ""), [&]() { bTacticNextStack();}, SDLK_SPACE);
-	btactEnd = std::make_shared<CButton>(Point(419,  4 + 556), "icm012.def", std::make_pair("", ""),  [&](){ bTacticPhaseEnd();}, SDLK_RETURN);
-
-	console = std::make_shared<BattleConsole>(menuBattle, Point(211, 4 + 556), Point(211, 4), Point(406,38));
+	REGISTER_BUILDER("battleConsole", &BattleWindow::buildBattleConsole);
+	
+	const JsonNode config(ResourceID("config/widgets/BattleWindow.json"));
+	
+	addCallback("options", std::bind(&BattleWindow::bOptionsf, this));
+	addCallback("surrender", std::bind(&BattleWindow::bSurrenderf, this));
+	addCallback("flee", std::bind(&BattleWindow::bFleef, this));
+	addCallback("autofight", std::bind(&BattleWindow::bAutofightf, this));
+	addCallback("spellbook", std::bind(&BattleWindow::bSpellf, this));
+	addCallback("wait", std::bind(&BattleWindow::bWaitf, this));
+	addCallback("defence", std::bind(&BattleWindow::bDefencef, this));
+	addCallback("consoleUp", std::bind(&BattleWindow::bConsoleUpf, this));
+	addCallback("consoleDown", std::bind(&BattleWindow::bConsoleDownf, this));
+	addCallback("tacticNext", std::bind(&BattleWindow::bTacticNextStack, this));
+	addCallback("tacticEnd", std::bind(&BattleWindow::bTacticPhaseEnd, this));
+	addCallback("alternativeAction", std::bind(&BattleWindow::bSwitchActionf, this));
+	
+	build(config);
+	
+	console = widget<BattleConsole>("console");
+
 	GH.statusbar = console;
 	owner.console = console;
 
@@ -104,6 +102,14 @@ BattleWindow::~BattleWindow()
 	CPlayerInterface::battleInt = nullptr;
 }
 
+std::shared_ptr<BattleConsole> BattleWindow::buildBattleConsole(const JsonNode & config) const
+{
+	auto rect = readRect(config["rect"]);
+	auto offset = readPosition(config["imagePosition"]);
+	auto background = widget<CPicture>("menuBattle");
+	return std::make_shared<BattleConsole>(background, rect.topLeft(), offset, rect.dimensions() );
+}
+
 void BattleWindow::hideQueue()
 {
 	Settings showQueue = settings.write["battle"]["showQueue"];
@@ -181,22 +187,34 @@ void BattleWindow::clickRight(tribool down, bool previousState)
 
 void BattleWindow::tacticPhaseStarted()
 {
+	auto menuBattle = widget<CIntObject>("menuBattle");
+	auto console = widget<CIntObject>("console");
+	auto menuTactics = widget<CIntObject>("menuTactics");
+	auto tacticNext = widget<CIntObject>("tacticNext");
+	auto tacticEnd = widget<CIntObject>("tacticEnd");
+
 	menuBattle->disable();
 	console->disable();
 
 	menuTactics->enable();
-	btactNext->enable();
-	btactEnd->enable();
+	tacticNext->enable();
+	tacticEnd->enable();
 }
 
 void BattleWindow::tacticPhaseEnded()
 {
+	auto menuBattle = widget<CIntObject>("menuBattle");
+	auto console = widget<CIntObject>("console");
+	auto menuTactics = widget<CIntObject>("menuTactics");
+	auto tacticNext = widget<CIntObject>("tacticNext");
+	auto tacticEnd = widget<CIntObject>("tacticEnd");
+
 	menuBattle->enable();
 	console->enable();
 
 	menuTactics->disable();
-	btactNext->disable();
-	btactEnd->disable();
+	tacticNext->disable();
+	tacticEnd->disable();
 }
 
 void BattleWindow::bOptionsf()
@@ -277,6 +295,57 @@ void BattleWindow::reallySurrender()
 	}
 }
 
+void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
+{
+	auto w = widget<CButton>("alternativeAction");
+	if(!w)
+		return;
+	
+	std::string iconName = variables["actionIconDefault"].String();
+	switch(action)
+	{
+		case PossiblePlayerBattleAction::ATTACK:
+			iconName = variables["actionIconAttack"].String();
+			break;
+			
+		case PossiblePlayerBattleAction::SHOOT:
+			iconName = variables["actionIconShoot"].String();
+			break;
+			
+		case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
+			iconName = variables["actionIconSpell"].String();
+			break;
+			
+		//TODO: figure out purpose of this icon
+		//case PossiblePlayerBattleAction::???:
+			//iconName = variables["actionIconWalk"].String();
+			//break;
+			
+		case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
+			iconName = variables["actionIconReturn"].String();
+			break;
+			
+		case PossiblePlayerBattleAction::WALK_AND_ATTACK:
+			iconName = variables["actionIconNoReturn"].String();
+			break;
+	}
+		
+	auto anim = std::make_shared<CAnimation>(iconName);
+	w->setImage(anim, false);
+}
+
+void BattleWindow::setAlternativeActions(const std::list<PossiblePlayerBattleAction> & actions)
+{
+	alternativeActions = actions;
+	defaultAction = PossiblePlayerBattleAction::INVALID;
+	if(alternativeActions.size() > 1)
+		defaultAction = alternativeActions.back();
+	if(!alternativeActions.empty())
+		showAlternativeActionIcon(alternativeActions.front());
+	else
+		showAlternativeActionIcon(defaultAction);
+}
+
 void BattleWindow::bAutofightf()
 {
 	if (owner.actionsController->spellcastingModeActive())
@@ -346,6 +415,33 @@ void BattleWindow::bSpellf()
 	}
 }
 
+void BattleWindow::bSwitchActionf()
+{
+	if(alternativeActions.empty())
+		return;
+	
+	if(alternativeActions.front() == defaultAction)
+	{
+		alternativeActions.push_back(alternativeActions.front());
+		alternativeActions.pop_front();
+	}
+	
+	auto actions = owner.actionsController->getPossibleActions();
+	if(!actions.empty() && actions.front() == alternativeActions.front())
+	{
+		owner.actionsController->removePossibleAction(alternativeActions.front());
+		showAlternativeActionIcon(defaultAction);
+	}
+	else
+	{
+		owner.actionsController->pushFrontPossibleAction(alternativeActions.front());
+		showAlternativeActionIcon(alternativeActions.front());
+	}
+	
+	alternativeActions.push_back(alternativeActions.front());
+	alternativeActions.pop_front();
+}
+
 void BattleWindow::bWaitf()
 {
 	if (owner.actionsController->spellcastingModeActive())
@@ -405,29 +501,43 @@ void BattleWindow::blockUI(bool on)
 
 	bool canWait = owner.stacksController->getActiveStack() ? !owner.stacksController->getActiveStack()->waitedThisTurn : false;
 
-	bOptions->block(on);
-	bFlee->block(on || !owner.curInt->cb->battleCanFlee());
-	bSurrender->block(on || owner.curInt->cb->battleGetSurrenderCost() < 0);
+	if(auto w = widget<CButton>("options"))
+		w->block(on);
+	if(auto w = widget<CButton>("flee"))
+		w->block(on || !owner.curInt->cb->battleCanFlee());
+	if(auto w = widget<CButton>("surrender"))
+		w->block(on || owner.curInt->cb->battleGetSurrenderCost() < 0);
+	if(auto w = widget<CButton>("cast"))
+		w->block(on || owner.tacticsMode || !canCastSpells);
+	if(auto w = widget<CButton>("wait"))
+		w->block(on || owner.tacticsMode || !canWait);
+	if(auto w = widget<CButton>("defence"))
+		w->block(on || owner.tacticsMode);
+	if(auto w = widget<CButton>("alternativeAction"))
+		w->block(on || owner.tacticsMode);
 
 	// block only if during enemy turn and auto-fight is off
 	// otherwise - crash on accessing non-exisiting active stack
-	bAutofight->block(!owner.curInt->isAutoFightOn && !owner.stacksController->getActiveStack());
+	if(auto w = widget<CButton>("options"))
+		w->block(!owner.curInt->isAutoFightOn && !owner.stacksController->getActiveStack());
 
-	if (owner.tacticsMode && btactEnd && btactNext)
+	auto btactEnd = widget<CButton>("tacticEnd");
+	auto btactNext = widget<CButton>("tacticNext");
+	if(owner.tacticsMode && btactEnd && btactNext)
 	{
 		btactNext->block(on);
 		btactEnd->block(on);
 	}
 	else
 	{
-		bConsoleUp->block(on);
-		bConsoleDown->block(on);
+		auto bConsoleUp = widget<CButton>("consoleUp");
+		auto bConsoleDown = widget<CButton>("consoleDown");
+		if(bConsoleUp && bConsoleDown)
+		{
+			bConsoleUp->block(on);
+			bConsoleDown->block(on);
+		}
 	}
-
-
-	bSpell->block(on || owner.tacticsMode || !canCastSpells);
-	bWait->block(on || owner.tacticsMode || !canWait);
-	bDefence->block(on || owner.tacticsMode);
 }
 
 void BattleWindow::showAll(SDL_Surface *to)
@@ -443,3 +553,10 @@ void BattleWindow::show(SDL_Surface *to)
 	CIntObject::show(to);
 	LOCPLINT->cingconsole->show(to);
 }
+
+void BattleWindow::close()
+{
+	if(GH.topInt().get() != this)
+		logGlobal->error("Only top interface must be closed");
+	GH.popInts(1);
+}

+ 17 - 19
client/battle/BattleWindow.h

@@ -10,6 +10,8 @@
 #pragma once
 
 #include "../gui/CIntObject.h"
+#include "../gui/InterfaceObjectConfigurable.h"
+#include "../../lib/battle/CBattleInfoCallback.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 class CStack;
@@ -23,29 +25,13 @@ class BattleRenderer;
 class StackQueue;
 
 /// GUI object that handles functionality of panel at the bottom of combat screen
-class BattleWindow : public WindowBase
+class BattleWindow : public InterfaceObjectConfigurable
 {
 	BattleInterface & owner;
 
 	std::shared_ptr<StackQueue> queue;
-
 	std::shared_ptr<BattleConsole> console;
 
-	std::shared_ptr<CPicture> menuTactics;
-	std::shared_ptr<CPicture> menuBattle;
-
-	std::shared_ptr<CButton> bOptions;
-	std::shared_ptr<CButton> bSurrender;
-	std::shared_ptr<CButton> bFlee;
-	std::shared_ptr<CButton> bAutofight;
-	std::shared_ptr<CButton> bSpell;
-	std::shared_ptr<CButton> bWait;
-	std::shared_ptr<CButton> bDefence;
-	std::shared_ptr<CButton> bConsoleUp;
-	std::shared_ptr<CButton> bConsoleDown;
-	std::shared_ptr<CButton> btactNext;
-	std::shared_ptr<CButton> btactEnd;
-
 	/// button press handling functions
 	void bOptionsf();
 	void bSurrenderf();
@@ -53,6 +39,7 @@ class BattleWindow : public WindowBase
 	void bAutofightf();
 	void bSpellf();
 	void bWaitf();
+	void bSwitchActionf();
 	void bDefencef();
 	void bConsoleUpf();
 	void bConsoleDownf();
@@ -62,17 +49,24 @@ class BattleWindow : public WindowBase
 	/// functions for handling actions after they were confirmed by popup window
 	void reallyFlee();
 	void reallySurrender();
+	
+	/// management of alternative actions
+	std::list<PossiblePlayerBattleAction> alternativeActions;
+	PossiblePlayerBattleAction defaultAction;
+	void showAlternativeActionIcon(PossiblePlayerBattleAction);
 
 	/// Toggle StackQueue visibility
 	void hideQueue();
 	void showQueue();
 
+	std::shared_ptr<BattleConsole> buildBattleConsole(const JsonNode &) const;
+
 public:
 	BattleWindow(BattleInterface & owner );
 	~BattleWindow();
 
-	/// Closes window once battle finished (explicit declaration to move into public visibility)
-	using WindowBase::close;
+	/// Closes window once battle finished
+	void close();
 
 	/// block all UI elements when player is not allowed to act, e.g. during enemy turn
 	void blockUI(bool on);
@@ -92,5 +86,9 @@ public:
 
 	/// Toggle UI to displaying battle log in place of tactics UI
 	void tacticPhaseEnded();
+
+	/// Set possible alternative options. If more than 1 - the last will be considered as default option
+	void setAlternativeActions(const std::list<PossiblePlayerBattleAction> &);
+
 };
 

+ 62 - 7
client/gui/InterfaceObjectConfigurable.cpp

@@ -25,11 +25,20 @@
 
 #include "../../lib/CGeneralTextHandler.h"
 
+static std::map<std::string, int> KeycodeMap{
+	{"up", SDLK_UP},
+	{"down", SDLK_DOWN},
+	{"left", SDLK_LEFT},
+	{"right", SDLK_RIGHT},
+	{"space", SDLK_SPACE},
+	{"enter", SDLK_RETURN}
+};
+
 
 InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config, int used, Point offset):
 	InterfaceObjectConfigurable(used, offset)
 {
-	init(config);
+	build(config);
 }
 
 InterfaceObjectConfigurable::InterfaceObjectConfigurable(int used, Point offset):
@@ -57,19 +66,32 @@ void InterfaceObjectConfigurable::addCallback(const std::string & callbackName,
 	callbacks[callbackName] = callback;
 }
 
-void InterfaceObjectConfigurable::init(const JsonNode &config)
+void InterfaceObjectConfigurable::deleteWidget(const std::string & name)
+{
+	auto iter = widgets.find(name);
+	if(iter != widgets.end())
+		widgets.erase(iter);
+}
+
+void InterfaceObjectConfigurable::build(const JsonNode &config)
 {
 	OBJ_CONSTRUCTION;
 	logGlobal->debug("Building configurable interface object");
-	for(auto & item : config["variables"].Struct())
+	auto * items = &config;
+	
+	if(config.getType() == JsonNode::JsonType::DATA_STRUCT)
 	{
-		logGlobal->debug("Read variable named %s", item.first);
-		variables[item.first] = item.second;
+		for(auto & item : config["variables"].Struct())
+		{
+			logGlobal->debug("Read variable named %s", item.first);
+			variables[item.first] = item.second;
+		}
+		
+		items = &config["items"];
 	}
 	
-	int unnamedObjectId = 0;
 	const std::string unnamedObjectPrefix = "__widget_";
-	for(const auto & item : config["items"].Vector())
+	for(const auto & item : items->Vector())
 	{
 		std::string name = item["name"].isNull()
 						? unnamedObjectPrefix + std::to_string(unnamedObjectId++)
@@ -210,6 +232,23 @@ std::pair<std::string, std::string> InterfaceObjectConfigurable::readHintText(co
 	return result;
 }
 
+int InterfaceObjectConfigurable::readKeycode(const JsonNode & config) const
+{
+	logGlobal->debug("Reading keycode");
+	if(config.getType() == JsonNode::JsonType::DATA_INTEGER)
+		return config.Integer();
+	
+	if(config.getType() == JsonNode::JsonType::DATA_STRING)
+	{
+		auto s = config.String();
+		if(s.size() == 1) //keyboard symbol
+			return s[0];
+		return KeycodeMap[s];
+	}
+	
+	return 0;
+}
+
 std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const
 {
 	logGlobal->debug("Building widget CPicture");
@@ -289,8 +328,24 @@ std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode
 			button->addOverlay(buildWidget(item));
 		}
 	}
+	if(!config["imageOrder"].isNull())
+	{
+		auto imgOrder = config["imageOrder"].Vector();
+		assert(imgOrder.size() >= 4);
+		button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
+	}
 	if(!config["callback"].isNull())
 		button->addCallback(std::bind(callbacks.at(config["callback"].String()), 0));
+	if(!config["hotkey"].isNull())
+	{
+		if(config["hotkey"].getType() == JsonNode::JsonType::DATA_VECTOR)
+		{
+			for(auto k : config["hotkey"].Vector())
+				button->assignedKeys.insert(readKeycode(k));
+		}
+		else
+			button->assignedKeys.insert(readKeycode(config["hotkey"]));
+	}
 	return button;
 }
 

+ 5 - 1
client/gui/InterfaceObjectConfigurable.h

@@ -39,7 +39,7 @@ protected:
 	void registerBuilder(const std::string &, BuilderFunction);
 	
 	//must be called after adding callbacks
-	void init(const JsonNode & config);
+	void build(const JsonNode & config);
 	
 	void addCallback(const std::string & callbackName, std::function<void(int)> callback);
 	JsonNode variables;
@@ -52,6 +52,8 @@ protected:
 			return nullptr;
 		return std::dynamic_pointer_cast<T>(iter->second);
 	}
+	
+	void deleteWidget(const std::string & name);
 		
 	//basic serializers
 	Point readPosition(const JsonNode &) const;
@@ -61,6 +63,7 @@ protected:
 	EFonts readFont(const JsonNode &) const;
 	std::string readText(const JsonNode &) const;
 	std::pair<std::string, std::string> readHintText(const JsonNode &) const;
+	int readKeycode(const JsonNode &) const;
 	
 	//basic widgets
 	std::shared_ptr<CPicture> buildPicture(const JsonNode &) const;
@@ -79,6 +82,7 @@ protected:
 	
 private:
 	
+	int unnamedObjectId = 0;
 	std::map<std::string, BuilderFunction> builders;
 	std::map<std::string, std::shared_ptr<CIntObject>> widgets;
 	std::map<std::string, std::function<void(int)>> callbacks;

+ 4 - 4
client/lobby/RandomMapTab.cpp

@@ -119,7 +119,7 @@ RandomMapTab::RandomMapTab():
 	}
 	
 	
-	init(config);
+	build(config);
 	
 	updateMapInfoByHost();
 }
@@ -338,7 +338,7 @@ TemplatesDropBox::ListItem::ListItem(const JsonNode & config, TemplatesDropBox &
 {
 	OBJ_CONSTRUCTION;
 	
-	init(config);
+	build(config);
 	
 	if(auto w = widget<CPicture>("hoverImage"))
 	{
@@ -412,7 +412,7 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size):
 	OBJ_CONSTRUCTION;
 	pos = randomMapTab.pos;
 	
-	init(config);
+	build(config);
 	
 	if(auto w = widget<CSlider>("slider"))
 	{
@@ -522,7 +522,7 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 		GH.popInt(GH.topInt());
 	});
 	
-	init(config);
+	build(config);
 	
 	center(pos);
 	

+ 135 - 0
config/widgets/battleWindow.json

@@ -0,0 +1,135 @@
+{
+	"items":
+	[
+		{
+			"type": "picture",
+			"name": "menuTactics",
+			"position": {"x": 0, "y": 556},
+			"image": "COPLACBR.bmp"
+		},
+		{
+			"type": "picture",
+			"name": "menuBattle",
+			"position": {"x": 0, "y": 556},
+			"image": "CBAR.bmp"
+		},
+		{
+			"type": "button",
+			"name": "options",
+			"position": {"x": 3, "y": 561},
+			"image": "icm003",
+			"zelp": 381,
+			"callback": "options",
+			"hotkey": "o"
+		},
+
+		{
+			"type": "button",
+			"name": "surrender",
+			"position": {"x": 54, "y": 561},
+			"image": "icm001",
+			"zelp": 379,
+			"callback": "surrender",
+			"hotkey": "s"
+		},
+
+		{
+			"type": "button",
+			"name": "flee",
+			"position": {"x": 105, "y": 561},
+			"image": "icm002",
+			"zelp": 380,
+			"callback": "flee",
+			"hotkey": "r"
+		},
+
+		{
+			"type": "button",
+			"name": "autofight",
+			"position": {"x": 157, "y": 561},
+			"image": "icm004",
+			"zelp": 382,
+			"callback": "autofight",
+			"hotkey": "a"
+		},
+
+		{
+			"type": "button",
+			"name": "cast",
+			"position": {"x": 645, "y": 561},
+			"image": "icm005",
+			"zelp": 385,
+			"callback": "spellbook",
+			"hotkey": "c"
+		},
+
+		{
+			"type": "button",
+			"name": "wait",
+			"position": {"x": 696, "y": 561},
+			"image": "icm006",
+			"zelp": 386,
+			"callback": "wait",
+			"hotkey": "w"
+		},
+
+		{
+			"type": "button",
+			"name": "defence",
+			"position": {"x": 747, "y": 561},
+			"image": "icm007",
+			"zelp": 387,
+			"callback": "defence",
+			"hotkey": ["d", "space"]
+		},
+
+		{
+			"type": "button",
+			"name": "consoleUp",
+			"position": {"x": 624, "y": 561},
+			"image": "ComSlide",
+			"zelp": "",
+			"callback": "consoleUp",
+			"imageOrder": [0, 1, 0, 0],
+			"hotkey": "up"
+		},
+
+		{
+			"type": "button",
+			"name": "consoleDown",
+			"position": {"x": 624, "y": 580},
+			"image": "ComSlide",
+			"zelp": "",
+			"callback": "consoleDown",
+			"imageOrder": [2, 3, 2, 2],
+			"hotkey": "down"
+		},
+
+		{
+			"type": "battleConsole",
+			"name": "console",
+			"imagePosition" : { "x" : 211, "y" : 4 },
+			"rect": {"x": 211, "y": 561, "w": 406, "h": 38}
+		},
+		
+		{
+			"type": "button",
+			"name": "tacticNext",
+			"position": {"x": 213, "y": 561},
+			"image": "icm011",
+			"zelp": "",
+			"callback": "tacticNext",
+			"hotkey": "space"
+		},
+		
+		{
+			"type": "button",
+			"name": "tacticEnd",
+			"position": {"x": 419, "y": 561},
+			"image": "icm012",
+			"zelp": "",
+			"callback": "tacticEnd",
+			"hotkey": "enter"
+		}
+	]
+}

+ 1 - 1
debian/control

@@ -2,7 +2,7 @@ Source: vcmi
 Section: games
 Priority: optional
 Maintainer: Ivan Savenko <[email protected]>
-Build-Depends: debhelper (>= 8), cmake, libsdl2-dev, libsdl2-image-dev, libsdl2-ttf-dev, libsdl2-mixer-dev, zlib1g-dev, libavformat-dev, libswscale-dev, libboost-dev (>=1.48), libboost-program-options-dev (>=1.48), libboost-filesystem-dev (>=1.48), libboost-system-dev (>=1.48), libboost-locale-dev (>=1.48), libboost-thread-dev (>=1.48),  qtbase5-dev, libtbb-dev, libfuzzylite-dev, libminizip-dev, libluajit-5.1-dev 
+Build-Depends: debhelper (>= 8), cmake, libsdl2-dev, libsdl2-image-dev, libsdl2-ttf-dev, libsdl2-mixer-dev, zlib1g-dev, libavformat-dev, libswscale-dev, libboost-dev (>=1.48), libboost-program-options-dev (>=1.48), libboost-filesystem-dev (>=1.48), libboost-system-dev (>=1.48), libboost-locale-dev (>=1.48), libboost-thread-dev (>=1.48),  qtbase5-dev, libtbb2-dev, libfuzzylite-dev, libminizip-dev, libluajit-5.1-dev 
 Standards-Version: 3.9.1
 Homepage: http://vcmi.eu
 Vcs-Git: git://github.com/vcmi/vcmi.git

+ 28 - 11
lib/IGameCallback.cpp

@@ -67,7 +67,7 @@ void CPrivilegedInfoCallback::getTilesInRange(std::unordered_set<int3, ShashInt3
 		return;
 	}
 	if(radious == CBuilding::HEIGHT_SKYSHIP) //reveal entire map
-		getAllTiles (tiles, player, -1, 0);
+		getAllTiles (tiles, player, -1, MapTerrainFilterMode::NONE);
 	else
 	{
 		const TeamState * team = !player ? nullptr : gs->getPlayerTeam(*player);
@@ -91,15 +91,13 @@ void CPrivilegedInfoCallback::getTilesInRange(std::unordered_set<int3, ShashInt3
 	}
 }
 
-void CPrivilegedInfoCallback::getAllTiles(std::unordered_set<int3, ShashInt3> & tiles, boost::optional<PlayerColor> Player, int level, int surface) const
+void CPrivilegedInfoCallback::getAllTiles(std::unordered_set<int3, ShashInt3> & tiles, boost::optional<PlayerColor> Player, int level, MapTerrainFilterMode tileFilterMode) const
 {
 	if(!!Player && *Player >= PlayerColor::PLAYER_LIMIT)
 	{
 		logGlobal->error("Illegal call to getAllTiles !");
 		return;
 	}
-	bool water = surface == 0 || surface == 2,
-		land = surface == 0 || surface == 1;
 
 	std::vector<int> floors;
 	if(level == -1)
@@ -112,16 +110,35 @@ void CPrivilegedInfoCallback::getAllTiles(std::unordered_set<int3, ShashInt3> &
 	else
 		floors.push_back(level);
 
-	for (auto zd : floors)
+	for(auto zd: floors)
 	{
-
-		for (int xd = 0; xd < gs->map->width; xd++)
+		for(int xd = 0; xd < gs->map->width; xd++)
 		{
-			for (int yd = 0; yd < gs->map->height; yd++)
+			for(int yd = 0; yd < gs->map->height; yd++)
 			{
-				if ((getTile (int3 (xd,yd,zd))->terType->isWater() && water)
-					|| (getTile (int3 (xd,yd,zd))->terType->isLand() && land))
-					tiles.insert(int3(xd,yd,zd));
+				bool isTileEligible = false;
+
+				switch(tileFilterMode)
+				{
+					case MapTerrainFilterMode::NONE:
+						isTileEligible = true;
+						break;
+					case MapTerrainFilterMode::WATER:
+						isTileEligible = getTile(int3(xd, yd, zd))->terType->isWater();
+						break;
+					case MapTerrainFilterMode::LAND:
+						isTileEligible = getTile(int3(xd, yd, zd))->terType->isLand();
+						break;
+					case MapTerrainFilterMode::LAND_CARTOGRAPHER:
+						isTileEligible = getTile(int3(xd, yd, zd))->terType->isSurfaceCartographerCompatible();
+						break;
+					case MapTerrainFilterMode::UNDERGROUND_CARTOGRAPHER:
+						isTileEligible = getTile(int3(xd, yd, zd))->terType->isUndergroundCartographerCompatible();
+						break;
+				}
+
+				if(isTileEligible)
+					tiles.insert(int3(xd, yd, zd));
 			}
 		}
 	}

+ 25 - 5
lib/IGameCallback.h

@@ -40,11 +40,31 @@ namespace scripting
 class DLL_LINKAGE CPrivilegedInfoCallback : public CGameInfoCallback
 {
 public:
-	CGameState * gameState();
-	void getFreeTiles (std::vector<int3> &tiles) const; //used for random spawns
-	void getTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, boost::optional<PlayerColor> player = boost::optional<PlayerColor>(), int mode = 0, int3::EDistanceFormula formula = int3::DIST_2D) const; //mode 1 - only unrevealed tiles; mode 0 - all, mode -1 -  only revealed
-	void getAllTiles (std::unordered_set<int3, ShashInt3> &tiles, boost::optional<PlayerColor> player = boost::optional<PlayerColor>(), int level=-1, int surface=0) const; //returns all tiles on given level (-1 - both levels, otherwise number of level); surface: 0 - land and water, 1 - only land, 2 - only water
-	void pickAllowedArtsSet(std::vector<const CArtifact*> &out, CRandomGenerator & rand); //gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant
+	enum class MapTerrainFilterMode
+	{
+		NONE = 0,
+		LAND = 1,
+		WATER = 2,
+		LAND_CARTOGRAPHER = 3,
+		UNDERGROUND_CARTOGRAPHER = 4
+	};
+
+	CGameState *gameState();
+
+	//used for random spawns
+	void getFreeTiles(std::vector<int3> &tiles) const;
+
+	//mode 1 - only unrevealed tiles; mode 0 - all, mode -1 -  only revealed
+	void getTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious,
+						 boost::optional<PlayerColor> player = boost::optional<PlayerColor>(), int mode = 0,
+						 int3::EDistanceFormula formula = int3::DIST_2D) const;
+
+	//returns all tiles on given level (-1 - both levels, otherwise number of level)
+	void getAllTiles(std::unordered_set<int3, ShashInt3> &tiles, boost::optional<PlayerColor> player = boost::optional<PlayerColor>(),
+					 int level = -1, MapTerrainFilterMode tileFilterMode = MapTerrainFilterMode::NONE) const;
+
+	void pickAllowedArtsSet(std::vector<const CArtifact *> &out,
+							CRandomGenerator &rand); //gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant
 	void getAllowedSpells(std::vector<SpellID> &out, ui16 level);
 
 	template<typename Saver>

+ 10 - 0
lib/Terrain.cpp

@@ -440,6 +440,16 @@ bool TerrainType::isUnderground() const
 	return passabilityType & PassabilityType::SUBTERRANEAN;
 }
 
+bool TerrainType::isSurfaceCartographerCompatible() const
+{
+	return isSurface();
+}
+
+bool TerrainType::isUndergroundCartographerCompatible() const
+{
+	return isLand() && isPassable() && !isSurface();
+}
+
 bool TerrainType::isTransitionRequired() const
 {
 	return transitionRequired;

+ 2 - 0
lib/Terrain.h

@@ -60,6 +60,8 @@ public:
 	bool isSurface() const;
 	bool isUnderground() const;
 	bool isTransitionRequired() const;
+	bool isSurfaceCartographerCompatible() const;
+	bool isUndergroundCartographerCompatible() const;
 		
 	operator std::string() const;
 	

+ 21 - 5
lib/mapObjects/MiscObjects.cpp

@@ -2068,18 +2068,34 @@ void CCartographer::onHeroVisit( const CGHeroInstance * h ) const
 
 void CCartographer::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
 {
-	if (answer) //if hero wants to buy map
+	if(answer) //if hero wants to buy map
 	{
-		cb->giveResource (hero->tempOwner, Res::GOLD, -1000);
+		cb->giveResource(hero->tempOwner, Res::GOLD, -1000);
 		FoWChange fw;
 		fw.mode = 1;
 		fw.player = hero->tempOwner;
 
 		//subIDs of different types of cartographers:
 		//water = 0; land = 1; underground = 2;
-		cb->getAllTiles (fw.tiles, hero->tempOwner, subID - 1, !subID + 1); //reveal appropriate tiles
-		cb->sendAndApply (&fw);
-		cb->setObjProperty (id, CCartographer::OBJPROP_VISITED, hero->tempOwner.getNum());
+
+		IGameCallback::MapTerrainFilterMode tileFilterMode = IGameCallback::MapTerrainFilterMode::NONE;
+
+		switch(subID)
+		{
+			case 0:
+				tileFilterMode = CPrivilegedInfoCallback::MapTerrainFilterMode::WATER;
+				break;
+			case 1:
+				tileFilterMode = CPrivilegedInfoCallback::MapTerrainFilterMode::LAND_CARTOGRAPHER;
+				break;
+			case 2:
+				tileFilterMode = CPrivilegedInfoCallback::MapTerrainFilterMode::UNDERGROUND_CARTOGRAPHER;
+				break;
+		}
+
+		cb->getAllTiles(fw.tiles, hero->tempOwner, -1, tileFilterMode); //reveal appropriate tiles
+		cb->sendAndApply(&fw);
+		cb->setObjProperty(id, CCartographer::OBJPROP_VISITED, hero->tempOwner.getNum());
 	}
 }