Pārlūkot izejas kodu

Make control panel configurable

nordsoft 2 gadi atpakaļ
vecāks
revīzija
660d25a335

+ 87 - 53
client/battle/BattleControlPanel.cpp

@@ -29,30 +29,34 @@
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CConfigHandler.h"
+#include "../../lib/filesystem/ResourceID.h"
 
 BattleControlPanel::BattleControlPanel(BattleInterface & owner, const Point & position):
 	owner(owner)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	REGISTER_BUILDER("battleConsole", &BattleControlPanel::buildBattleConsole);
+	
 	pos += position;
-
-	//preparing buttons and console
-	bOptions = std::make_shared<CButton>    (Point(  3,  5), "icm003.def", CGI->generaltexth->zelp[381], std::bind(&BattleControlPanel::bOptionsf,this), SDLK_o);
-	bSurrender = std::make_shared<CButton>  (Point( 54,  5), "icm001.def", CGI->generaltexth->zelp[379], std::bind(&BattleControlPanel::bSurrenderf,this), SDLK_s);
-	bFlee = std::make_shared<CButton>       (Point(105,  5), "icm002.def", CGI->generaltexth->zelp[380], std::bind(&BattleControlPanel::bFleef,this), SDLK_r);
-	bAutofight = std::make_shared<CButton>  (Point(157,  5), "icm004.def", CGI->generaltexth->zelp[382], std::bind(&BattleControlPanel::bAutofightf,this), SDLK_a);
-	bSpell = std::make_shared<CButton>      (Point(645,  5), "icm005.def", CGI->generaltexth->zelp[385], std::bind(&BattleControlPanel::bSpellf,this), SDLK_c);
-	bWait = std::make_shared<CButton>       (Point(696,  5), "icm006.def", CGI->generaltexth->zelp[386], std::bind(&BattleControlPanel::bWaitf,this), SDLK_w);
-	bDefence = std::make_shared<CButton>    (Point(747,  5), "icm007.def", CGI->generaltexth->zelp[387], std::bind(&BattleControlPanel::bDefencef,this), SDLK_d);
-	bSwitchAction = std::make_shared<CButton>(Point(589,  5), "icmalt00", CGI->generaltexth->zelp[387], std::bind(&BattleControlPanel::bSwitchActionf,this), SDLK_r);
-	bConsoleUp = std::make_shared<CButton>  (Point(578,  5), "ComSlide.def", std::make_pair("", ""),     std::bind(&BattleControlPanel::bConsoleUpf,this), SDLK_UP);
-	bConsoleDown = std::make_shared<CButton>(Point(578, 24), "ComSlide.def", std::make_pair("", ""),     std::bind(&BattleControlPanel::bConsoleDownf,this), SDLK_DOWN);
-
-	bDefence->assignedKeys.insert(SDLK_SPACE);
-	bConsoleUp->setImageOrder(0, 1, 0, 0);
-	bConsoleDown->setImageOrder(2, 3, 2, 2);
-
-	console = std::make_shared<BattleConsole>(Rect(211, 4, 350 ,38));
+	
+	const JsonNode config(ResourceID("config/widgets/battleControlPanel.json"));
+	
+	addCallback("options", std::bind(&BattleControlPanel::bOptionsf, this));
+	addCallback("surrender", std::bind(&BattleControlPanel::bSurrenderf, this));
+	addCallback("flee", std::bind(&BattleControlPanel::bFleef, this));
+	addCallback("autofight", std::bind(&BattleControlPanel::bAutofightf, this));
+	addCallback("spellbook", std::bind(&BattleControlPanel::bSpellf, this));
+	addCallback("wait", std::bind(&BattleControlPanel::bWaitf, this));
+	addCallback("defence", std::bind(&BattleControlPanel::bDefencef, this));
+	addCallback("consoleUp", std::bind(&BattleControlPanel::bConsoleUpf, this));
+	addCallback("consoleDown", std::bind(&BattleControlPanel::bConsoleDownf, this));
+	addCallback("tacticNext", std::bind(&BattleControlPanel::bTacticNextStack, this));
+	addCallback("tacticEnd", std::bind(&BattleControlPanel::bTacticPhaseEnd, this));
+	addCallback("alternativeAction", std::bind(&BattleControlPanel::bSwitchActionf, this));
+	
+	build(config);
+	
+	console = widget<BattleConsole>("console");
 	GH.statusbar = console;
 
 	if ( owner.tacticsMode )
@@ -61,41 +65,52 @@ BattleControlPanel::BattleControlPanel(BattleInterface & owner, const Point & po
 		tacticPhaseEnded();
 }
 
+std::shared_ptr<BattleConsole> BattleControlPanel::buildBattleConsole(const JsonNode & config) const
+{
+	return std::make_shared<BattleConsole>(readRect(config["rect"]));
+}
+
 void BattleControlPanel::show(SDL_Surface * to)
 {
 	//show menu before all other elements to keep it in background
-	menu->show(to);
+	if(auto w = widget<CPicture>("menu"))
+		w->show(to);
 	CIntObject::show(to);
 }
 
 void BattleControlPanel::showAll(SDL_Surface * to)
 {
 	//show menu before all other elements to keep it in background
-	menu->showAll(to);
+	if(auto w = widget<CPicture>("menu"))
+		w->showAll(to);
 	CIntObject::showAll(to);
 }
 
 
 void BattleControlPanel::tacticPhaseStarted()
 {
-	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	build(variables["tacticItems"]);
 
-	btactNext = std::make_shared<CButton>(Point(213, 4), "icm011.def", std::make_pair("", ""), [&]() { bTacticNextStack();}, SDLK_SPACE);
-	btactEnd = std::make_shared<CButton>(Point(419,  4), "icm012.def", std::make_pair("", ""),  [&](){ bTacticPhaseEnd();}, SDLK_RETURN);
-	menu = std::make_shared<CPicture>("COPLACBR.BMP", 0, 0);
-	menu->colorize(owner.curInt->playerID);
-	menu->recActions &= ~(SHOWALL | UPDATE);
+	if(auto w = widget<CPicture>("menu"))
+	{
+		w->colorize(owner.curInt->playerID);
+		w->recActions &= ~(SHOWALL | UPDATE);
+	}
 }
 void BattleControlPanel::tacticPhaseEnded()
 {
-	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	build(variables["battleItems"]);
 
-	btactNext.reset();
-	btactEnd.reset();
+	if(auto w = widget<CButton>("tacticNext"))
+		w.reset();
+	if(auto w = widget<CButton>("tacticEnd"))
+		w.reset();
 
-	menu = std::make_shared<CPicture>("CBAR.BMP", 0, 0);
-	menu->colorize(owner.curInt->playerID);
-	menu->recActions &= ~(SHOWALL | UPDATE);
+	if(auto w = widget<CPicture>("menu"))
+	{
+		w->colorize(owner.curInt->playerID);
+		w->recActions &= ~(SHOWALL | UPDATE);
+	}
 }
 
 void BattleControlPanel::bOptionsf()
@@ -178,36 +193,41 @@ void BattleControlPanel::reallySurrender()
 
 void BattleControlPanel::showAlternativeActionIcon(PossiblePlayerBattleAction action)
 {
-	std::string iconName = "icmalt00";
+	auto w = widget<CButton>("alternativeAction");
+	if(!w)
+		return;
+	
+	std::string iconName = variables["actionIconDefault"].String();
 	switch(action)
 	{
 		case PossiblePlayerBattleAction::ATTACK:
-			iconName = "icmalt01";
+			iconName = variables["actionIconAttack"].String();
 			break;
 			
 		case PossiblePlayerBattleAction::SHOOT:
-			iconName = "icmalt02";
+			iconName = variables["actionIconShoot"].String();
 			break;
 			
 		case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
-			iconName = "icmalt03";
+			iconName = variables["actionIconSpell"].String();
 			break;
 			
-		//case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
-			//iconName = "icmalt04";
+		//TODO: figure out purpose of this icon
+		//case PossiblePlayerBattleAction::???:
+			//iconName = variables["actionIconWalk"].String();
 			//break;
 			
 		case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
-			iconName = "icmalt05";
+			iconName = variables["actionIconReturn"].String();
 			break;
 			
 		case PossiblePlayerBattleAction::WALK_AND_ATTACK:
-			iconName = "icmalt06";
+			iconName = variables["actionIconNoReturn"].String();
 			break;
 	}
 		
 	auto anim = std::make_shared<CAnimation>(iconName);
-	bSwitchAction->setImage(anim, false);
+	w->setImage(anim, false);
 }
 
 void BattleControlPanel::setAlternativeActions(const std::list<PossiblePlayerBattleAction> & actions)
@@ -377,27 +397,41 @@ void BattleControlPanel::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);
 }

+ 5 - 17
client/battle/BattleControlPanel.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../gui/CIntObject.h"
+#include "../gui/InterfaceObjectConfigurable.h"
 #include "../../lib/battle/CBattleInfoCallback.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -22,24 +23,11 @@ class BattleInterface;
 class BattleConsole;
 
 /// GUI object that handles functionality of panel at the bottom of combat screen
-class BattleControlPanel : public CIntObject
-{
+class BattleControlPanel : public InterfaceObjectConfigurable
+{	
 	BattleInterface & owner;
-
-	std::shared_ptr<CPicture> menu;
-
-	std::shared_ptr<CButton> bOptions;
-	std::shared_ptr<CButton> bSurrender;
-	std::shared_ptr<CButton> bFlee;
-	std::shared_ptr<CButton> bAutofight;
-	std::shared_ptr<CButton> bSwitchAction;
-	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;
+	
+	std::shared_ptr<BattleConsole> buildBattleConsole(const JsonNode &) const;
 
 	/// button press handling functions
 	void bOptionsf();

+ 55 - 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,25 @@ void InterfaceObjectConfigurable::addCallback(const std::string & callbackName,
 	callbacks[callbackName] = callback;
 }
 
-void InterfaceObjectConfigurable::init(const JsonNode &config)
+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 +225,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 +321,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;
 }
 

+ 3 - 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;
@@ -61,6 +61,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 +80,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"))
 	{
@@ -414,7 +414,7 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size):
 	pos.w = randomMapTab.pos.w;
 	pos.h = randomMapTab.pos.h;
 	
-	init(config);
+	build(config);
 	
 	if(auto w = widget<CSlider>("slider"))
 	{
@@ -524,7 +524,7 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 		GH.popInt(GH.topInt());
 	});
 	
-	init(config);
+	build(config);
 	
 	center(pos);
 	

+ 144 - 0
config/widgets/battleControlPanel.json

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