Browse Source

Merge pull request #6313 from Laserlicht/custom_map_size

Custom map size in RMG
Ivan Savenko 1 week ago
parent
commit
423c49d465

+ 5 - 0
Mods/vcmi/Content/config/english.json

@@ -171,6 +171,11 @@
 	"vcmi.lobby.battleOnlySpellRemove" : "Remove spell",
 	"vcmi.lobby.templatesSelect.hover" : "Templates",
 	"vcmi.lobby.templatesSelect.help" : "Search and select template",
+	"vcmi.lobby.customRmgSize.title" : "Select custom size",
+	"vcmi.lobby.customRmgSize.experimental" : "Multilevel support is highly experimental yet. Expect issues.",
+	"vcmi.lobby.customRmgSize.0" : "Width",
+	"vcmi.lobby.customRmgSize.1" : "Height",
+	"vcmi.lobby.customRmgSize.2" : "Layers",
 
 	"vcmi.broadcast.failedLoadGame" : "Failed to load game",
 	"vcmi.broadcast.command" : "Use '!help' to list available commands",

+ 5 - 0
Mods/vcmi/Content/config/german.json

@@ -171,6 +171,11 @@
 	"vcmi.lobby.battleOnlySpellRemove" : "Zauber entfernen",
 	"vcmi.lobby.templatesSelect.hover" : "Templates",
 	"vcmi.lobby.templatesSelect.help" : "Suche und wähle Template aus",
+	"vcmi.lobby.customRmgSize.title" : "Wähle Größe",
+	"vcmi.lobby.customRmgSize.experimental" : "Die Multi-Ebenen-Unterstützung befindet sich noch in einem sehr experimentellen Stadium. Es ist mit Problemen zu rechnen.",
+	"vcmi.lobby.customRmgSize.0" : "Breite",
+	"vcmi.lobby.customRmgSize.1" : "Höhe",
+	"vcmi.lobby.customRmgSize.2" : "Ebenen",
 
 	"vcmi.broadcast.failedLoadGame" : "Spiel konnte nicht geladen werden",
 	"vcmi.broadcast.command" : "Benutze '!help' um alle verfügbaren Befehle aufzulisten",

+ 3 - 0
client/adventureMap/CMinimap.cpp

@@ -244,7 +244,10 @@ void CMinimap::onMapViewMoved(const Rect & visibleArea, int mapLevel)
 		update();
 	}
 	else
+	{
+		setRedrawParent(true); // needed for non square map to redraw black background when viewarea rectangle is moved
 		redraw();
+	}
 }
 
 void CMinimap::setAIRadar(bool on)

+ 5 - 5
client/lobby/CSelectionBase.cpp

@@ -155,9 +155,9 @@ InfoCard::InfoCard()
 	pos.x += 393;
 	pos.y += 6;
 
-	labelSaveDate = std::make_shared<CLabel>(310, 38, FONT_SMALL, ETextAlignment::BOTTOMRIGHT, Colors::WHITE);
-	labelMapSize = std::make_shared<CLabel>(333, 56, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE);
-	mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, "", SEL->screenType == ESelectionScreen::campaignList ? 325 : 285);
+	labelSaveDate = std::make_shared<CLabel>(305, 38, FONT_SMALL, ETextAlignment::BOTTOMRIGHT, Colors::WHITE);
+	labelMapSize = std::make_shared<CLabel>(327, 57, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE);
+	mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, "", SEL->screenType == ESelectionScreen::campaignList ? 325 : 280);
 	Rect descriptionRect(26, 149, 320, 115);
 	mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
 	playerListBg = std::make_shared<CPicture>(ImagePath::builtin("CHATPLUG.bmp"), 16, 276);
@@ -183,7 +183,7 @@ InfoCard::InfoCard()
 		parent->children.pop_back();
 		pos.w = background->pos.w;
 		pos.h = background->pos.h;
-		iconsMapSizes = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRMPSZ"), 4, 0, 318, 22); //let it be custom size (frame 4) by default
+		iconsMapSizes = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRMPSZ"), 4, 0, 313, 25); //let it be custom size (frame 4) by default
 
 		iconDifficulty = std::make_shared<CToggleGroup>(0);
 		{
@@ -255,7 +255,7 @@ void InfoCard::changeSelection()
 
 	const CMapHeader * header = mapInfo->mapHeader.get();
 
-	labelMapSize->setText(std::to_string(header->width) + "x" + std::to_string(header->height));
+	labelMapSize->setText(std::to_string(header->width) + "x" + std::to_string(header->height) + "x" + std::to_string(header->mapLevels));
 	iconsMapSizes->setFrame(mapInfo->getMapSizeIconId());
 
 	iconsVictoryCondition->setFrame(header->victoryIconIndex);

+ 81 - 14
client/lobby/RandomMapTab.cpp

@@ -19,6 +19,7 @@
 #include "../GameInstance.h"
 #include "../gui/MouseButton.h"
 #include "../gui/WindowHandler.h"
+#include "../gui/Shortcut.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/ComboBox.h"
 #include "../widgets/Buttons.h"
@@ -26,6 +27,8 @@
 #include "../widgets/ObjectLists.h"
 #include "../widgets/Slider.h"
 #include "../widgets/TextControls.h"
+#include "../widgets/GraphicalPrimitiveCanvas.h"
+#include "../widgets/CTextInput.h"
 #include "../windows/GUIClasses.h"
 #include "../windows/InfoWindows.h"
 
@@ -49,19 +52,13 @@ RandomMapTab::RandomMapTab():
 	recActions = 0;
 	mapGenOptions = std::make_shared<CMapGenOptions>();
 	
-	addCallback("toggleMapSize", [&](int btnId)
+	addCallback("toggleMapSize", [this](int btnId)
 	{
-		auto mapSizeVal = getPossibleMapSizes();
-		mapGenOptions->setWidth(mapSizeVal[btnId]);
-		mapGenOptions->setHeight(mapSizeVal[btnId]);
-		if(mapGenOptions->getMapTemplate())
-			if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), mapGenOptions->getLevels()}))
-				setTemplate(nullptr);
-		updateMapInfoByHost();
+		onToggleMapSize(btnId);
 	});
 	addCallback("toggleTwoLevels", [&](bool on)
 	{
-		mapGenOptions->setLevels(on ? 2 : 1); // TODO: multilevel support
+		mapGenOptions->setLevels(on ? 2 : 1);
 		if(mapGenOptions->getMapTemplate())
 			if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), mapGenOptions->getLevels()}))
 				setTemplate(nullptr);
@@ -198,6 +195,41 @@ RandomMapTab::RandomMapTab():
 	loadOptions();
 }
 
+void RandomMapTab::onToggleMapSize(int btnId)
+{
+	if(btnId == -1)
+		return;
+
+	auto mapSizeVal = getStandardMapSizes();
+
+	auto setTemplateForSize = [this](){
+		if(mapGenOptions->getMapTemplate())
+			if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), mapGenOptions->getLevels()}))
+				setTemplate(nullptr);
+		updateMapInfoByHost();
+	};
+
+	if(btnId == mapSizeVal.size() - 1)
+	{
+		ENGINE->windows().createAndPushWindow<SetSizeWindow>(int3(mapGenOptions->getWidth(), mapGenOptions->getHeight(), mapGenOptions->getLevels()), [this, setTemplateForSize](int3 ret){
+			if(ret.z > 2)
+			{
+				std::shared_ptr<CInfoWindow> temp = CInfoWindow::create(LIBRARY->generaltexth->translate("vcmi.lobby.customRmgSize.experimental"), PlayerColor(0), {}); //TODO: multilevel support
+				ENGINE->windows().pushWindow(temp);
+			}
+			mapGenOptions->setWidth(ret.x);
+			mapGenOptions->setHeight(ret.y);
+			mapGenOptions->setLevels(ret.z);
+			setTemplateForSize();
+		});
+		return;
+	}
+
+	mapGenOptions->setWidth(mapSizeVal[btnId]);
+	mapGenOptions->setHeight(mapSizeVal[btnId]);
+	setTemplateForSize();
+}
+
 void RandomMapTab::updateMapInfoByHost()
 {
 	if(GAME->server().isGuest())
@@ -345,18 +377,19 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 	
 	if(auto w = widget<CToggleGroup>("groupMapSize"))
 	{
+		const auto & mapSizes = getStandardMapSizes();
 		for(auto toggle : w->buttons)
 		{
 			if(auto button = std::dynamic_pointer_cast<CToggleButton>(toggle.second))
 			{
-				const auto & mapSizes = getPossibleMapSizes();
 				int3 size( mapSizes[toggle.first], mapSizes[toggle.first], mapGenOptions->getLevels());
 
 				bool sizeAllowed = !mapGenOptions->getMapTemplate() || mapGenOptions->getMapTemplate()->matchesSize(size);
-				button->block(!sizeAllowed);
+				button->block(!sizeAllowed && !(toggle.first == mapSizes.size() - 1));
 			}
 		}
-		w->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
+		auto position = vstd::find_pos(getStandardMapSizes(), opts->getWidth());
+		w->setSelected(position == mapSizes.size() - 1 || opts->getWidth() != opts->getHeight() ? -1 : position);
 	}
 	if(auto w = widget<CToggleButton>("buttonTwoLevels"))
 	{
@@ -366,7 +399,7 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 			auto sizes = mapGenOptions->getMapTemplate()->getMapSizes();
 			possibleLevelCount = sizes.second.z - sizes.first.z + 1;
 		}
-		w->setSelected(opts->getLevels() == 2); // TODO: multilevel support
+		w->setSelectedSilent(opts->getLevels() == 2);
 		w->block(possibleLevelCount < 2);
 	}
 	if(auto w = widget<CToggleGroup>("groupMaxPlayers"))
@@ -457,7 +490,7 @@ void RandomMapTab::deactivateButtonsFrom(CToggleGroup & group, const std::set<in
 	}
 }
 
-std::vector<int> RandomMapTab::getPossibleMapSizes()
+std::vector<int> RandomMapTab::getStandardMapSizes()
 {
 	return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_GIANT};
 }
@@ -653,3 +686,37 @@ void RandomMapTab::loadOptions()
 
 	// TODO: Save & load difficulty?
 }
+
+SetSizeWindow::SetSizeWindow(int3 initSize, std::function<void(int3)> cb)
+	: CWindowObject(BORDERED)
+{
+	OBJECT_CONSTRUCTION;
+
+	pos.w = 200;
+	pos.h = 122;
+
+	updateShadow();
+	center();
+
+	background = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
+	background->setPlayerColor(PlayerColor(1));
+	buttonOk = std::make_shared<CButton>(Point(68, 80), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this, cb](){
+		close();
+		if(cb)
+			cb(int3(std::max(1, std::stoi(numInputs[0]->getText())), std::max(1, std::stoi(numInputs[1]->getText())), std::max(1, std::stoi(numInputs[2]->getText()))));
+	}, EShortcut::GLOBAL_ACCEPT);
+
+	titles.push_back(std::make_shared<CLabel>(100, 15, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE, LIBRARY->generaltexth->translate("vcmi.lobby.customRmgSize.title")));
+
+	for(int i = 0; i < 3; i++)
+	{
+		Rect r(30 + i * 50, 50, 40, 20);
+		rectangles.push_back(std::make_shared<TransparentFilledRectangle>(r, ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1));
+		titles.push_back(std::make_shared<CLabel>(50 + i * 50, 40, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, LIBRARY->generaltexth->translate("vcmi.lobby.customRmgSize." + std::to_string(i))));
+		numInputs.push_back(std::make_shared<CTextInput>(r, EFonts::FONT_SMALL, ETextAlignment::CENTER, false));
+		numInputs.back()->setFilterNumber(0, i < 2 ? 999 : 9);
+	}
+	numInputs[0]->setText(std::to_string(initSize.x));
+	numInputs[1]->setText(std::to_string(initSize.y));
+	numInputs[2]->setText(std::to_string(initSize.z));
+}

+ 16 - 1
client/lobby/RandomMapTab.h

@@ -27,6 +27,8 @@ class CLabel;
 class CLabelGroup;
 class CSlider;
 class CPicture;
+class CTextInput;
+class TransparentFilledRectangle;
 
 class RandomMapTab : public InterfaceObjectConfigurable
 {
@@ -45,7 +47,8 @@ public:
 
 private:
 	void deactivateButtonsFrom(CToggleGroup & group, const std::set<int> & allowed);
-	std::vector<int> getPossibleMapSizes();
+	std::vector<int> getStandardMapSizes();
+	void onToggleMapSize(int btnId);
 
 	std::shared_ptr<CMapInfo> mapInfo;
 	std::shared_ptr<CMapGenOptions> mapGenOptions;
@@ -81,3 +84,15 @@ class TeamAlignments: public CWindowObject
 public:
 	TeamAlignments(RandomMapTab & randomMapTab);
 };
+
+class SetSizeWindow: public CWindowObject
+{
+	std::shared_ptr<FilledTexturePlayerColored> background;
+	std::vector<std::shared_ptr<CLabel>> titles;
+	std::shared_ptr<CButton> buttonOk;
+
+	std::vector<std::shared_ptr<CTextInput>> numInputs;
+	std::vector<std::shared_ptr<TransparentFilledRectangle>> rectangles;
+public:
+	SetSizeWindow(int3 initSize, std::function<void(int3)> cb);
+};

+ 1 - 1
lib/mapObjects/MiscObjects.cpp

@@ -486,7 +486,7 @@ void CGSubterraneanGate::initObj(IGameRandomizer & gameRandomizer)
 void CGSubterraneanGate::postInit(IGameInfoCallback * cb) //matches subterranean gates into pairs
 {
 	//split on underground and surface gates
-	std::vector<CGSubterraneanGate *> gatesSplit[2]; //surface and underground gates
+	std::vector<std::vector<CGSubterraneanGate *>> gatesSplit(cb->gameState().getMap().mapLevels); //surface and underground gates
 	for(auto gate : cb->gameState().getMap().getObjects<CGSubterraneanGate>())
 	{
 		gatesSplit[gate->visitablePos().z].push_back(gate);