From ecfe09f6b1f11c401fc20374d262da03f47d1af8 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 1 Aug 2025 00:37:32 +0200 Subject: [PATCH 1/4] multilevel support --- client/adventureMap/AdventureMapShortcuts.cpp | 2 +- client/adventureMap/AdventureMapWidget.cpp | 2 +- client/lobby/RandomMapTab.cpp | 12 ++-- client/windows/CMapOverview.cpp | 3 +- client/windows/InfoWindows.cpp | 4 +- lib/callback/MapInfoCallback.cpp | 2 +- lib/gameState/GameStatistics.cpp | 2 +- lib/mapping/CMap.cpp | 3 +- lib/mapping/CMap.h | 2 +- lib/mapping/CMapHeader.cpp | 4 +- lib/mapping/CMapHeader.h | 20 ++++++- lib/mapping/CMapOperation.cpp | 11 ++-- lib/mapping/MapFormatH3M.cpp | 2 +- lib/mapping/MapFormatJson.cpp | 60 ++++++++++--------- lib/rmg/CMapGenOptions.cpp | 25 ++++---- lib/rmg/CMapGenOptions.h | 23 +++++-- lib/rmg/CMapGenerator.cpp | 2 +- lib/rmg/CZonePlacer.cpp | 55 ++++++++++------- lib/rmg/CZonePlacer.h | 2 +- lib/serializer/ESerializationVersion.h | 3 +- mapeditor/mainwindow.cpp | 4 +- mapeditor/mapcontroller.cpp | 2 +- mapeditor/maphandler.cpp | 2 +- mapeditor/scenelayer.cpp | 4 +- mapeditor/windownewmap.cpp | 8 +-- test/map/CMapFormatTest.cpp | 2 +- test/map/MapComparer.cpp | 2 +- 27 files changed, 156 insertions(+), 107 deletions(-) diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index 81b809494..ed4923bb9 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -154,7 +154,7 @@ void AdventureMapShortcuts::worldViewScale4x() void AdventureMapShortcuts::switchMapLevel() { int maxLevels = GAME->interface()->cb->getMapSize().z; - if (maxLevels < 2) + if (maxLevels < 2) // TODO: multilevel support return; owner.hotkeySwitchMapLevel(); diff --git a/client/adventureMap/AdventureMapWidget.cpp b/client/adventureMap/AdventureMapWidget.cpp index baec753e7..f5fc3f8e2 100644 --- a/client/adventureMap/AdventureMapWidget.cpp +++ b/client/adventureMap/AdventureMapWidget.cpp @@ -410,7 +410,7 @@ void AdventureMapWidget::updateActiveStateChildden(CIntObject * widget) if (container->disableCondition == "heroSleeping") container->setEnabled(shortcuts->optionHeroSleeping()); - if (container->disableCondition == "mapLayerSurface") + if (container->disableCondition == "mapLayerSurface") // TODO: multilevel support container->setEnabled(shortcuts->optionMapLevelSurface()); if (container->disableCondition == "mapLayerUnderground") diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 5d6a52332..4fc531011 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -54,15 +54,15 @@ RandomMapTab::RandomMapTab(): mapGenOptions->setWidth(mapSizeVal[btnId]); mapGenOptions->setHeight(mapSizeVal[btnId]); if(mapGenOptions->getMapTemplate()) - if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()})) + if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), mapGenOptions->getLevels()})) setTemplate(nullptr); updateMapInfoByHost(); }); addCallback("toggleTwoLevels", [&](bool on) { - mapGenOptions->setHasTwoLevels(on); + mapGenOptions->setLevels(on ? 2 : 1); // TODO: multilevel support if(mapGenOptions->getMapTemplate()) - if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()})) + if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), mapGenOptions->getLevels()})) setTemplate(nullptr); updateMapInfoByHost(); }); @@ -202,7 +202,7 @@ void RandomMapTab::updateMapInfoByHost() mapInfo->mapHeader->difficulty = EMapDifficulty::NORMAL; mapInfo->mapHeader->height = mapGenOptions->getHeight(); mapInfo->mapHeader->width = mapGenOptions->getWidth(); - mapInfo->mapHeader->twoLevel = mapGenOptions->getHasTwoLevels(); + mapInfo->mapHeader->mapLevels = mapGenOptions->getLevels(); // Generate player information int playersToGen = mapGenOptions->getMaxPlayersCount(); @@ -321,7 +321,7 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr opts) if(auto button = std::dynamic_pointer_cast(toggle.second)) { const auto & mapSizes = getPossibleMapSizes(); - int3 size( mapSizes[toggle.first], mapSizes[toggle.first], 1 + mapGenOptions->getHasTwoLevels()); + int3 size( mapSizes[toggle.first], mapSizes[toggle.first], mapGenOptions->getLevels()); bool sizeAllowed = !mapGenOptions->getMapTemplate() || mapGenOptions->getMapTemplate()->matchesSize(size); button->block(!sizeAllowed); @@ -335,7 +335,7 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr opts) bool undergoundAllowed = !mapGenOptions->getMapTemplate() || mapGenOptions->getMapTemplate()->matchesSize(size); - w->setSelected(opts->getHasTwoLevels()); + w->setSelected(opts->getLevels() == 2); // TODO: multilevel support w->block(!undergoundAllowed); } if(auto w = widget("groupMaxPlayers")) diff --git a/client/windows/CMapOverview.cpp b/client/windows/CMapOverview.cpp index 04f4c5762..0b2862407 100644 --- a/client/windows/CMapOverview.cpp +++ b/client/windows/CMapOverview.cpp @@ -121,7 +121,7 @@ std::vector> CMapOverviewWidget::createMinimaps(std { std::vector> ret; - for(int i = 0; i < (map->twoLevel ? 2 : 1); i++) + for(int i = 0; i < map->levels(); i++) ret.push_back(createMinimapForLayer(map, i)); return ret; @@ -129,6 +129,7 @@ std::vector> CMapOverviewWidget::createMinimaps(std std::shared_ptr CMapOverviewWidget::buildDrawMinimap(const JsonNode & config) const { + // TODO: multilevel support logGlobal->debug("Building widget drawMinimap"); auto rect = readRect(config["rect"]); diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index 98997550e..10bd84aa3 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -351,7 +351,7 @@ MinimapWithIcons::MinimapWithIcons(const Point & position) Rect borderSurface(10, 40, 147, 147); Rect borderUnderground(166, 40, 147, 147); - bool singleLevelMap = GAME->interface()->cb->getMapSize().z == 1; + bool singleLevelMap = GAME->interface()->cb->getMapSize().z == 1; // TODO: multilevel support if (singleLevelMap) { @@ -375,7 +375,7 @@ void MinimapWithIcons::addIcon(const int3 & coordinates, const ImagePath & image Rect areaSurface(11, 41, 144, 144); Rect areaUnderground(167, 41, 144, 144); - bool singleLevelMap = GAME->interface()->cb->getMapSize().z == 1; + bool singleLevelMap = GAME->interface()->cb->getMapSize().z == 1; // TODO: multilevel support if (singleLevelMap) areaSurface.x += 78; diff --git a/lib/callback/MapInfoCallback.cpp b/lib/callback/MapInfoCallback.cpp index 1296fd48c..65eb71e2c 100644 --- a/lib/callback/MapInfoCallback.cpp +++ b/lib/callback/MapInfoCallback.cpp @@ -97,7 +97,7 @@ bool MapInfoCallback::isAllowed(SecondarySkill id) const int3 MapInfoCallback::getMapSize() const { - return int3(getMapConstPtr()->width, getMapConstPtr()->height, getMapConstPtr()->twoLevel ? 2 : 1); + return int3(getMapConstPtr()->width, getMapConstPtr()->height, getMapConstPtr()->levels()); } void MapInfoCallback::getAllowedSpells(std::vector & out, std::optional level) diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index c26de6904..3bc6c4cf3 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -325,7 +325,7 @@ float Statistic::getMapExploredRatio(const CGameState * gs, PlayerColor player) float visible = 0.0; float numTiles = 0.0; - for(int layer = 0; layer < (gs->getMap().twoLevel ? 2 : 1); layer++) + for(int layer = 0; layer < gs->getMap().levels(); layer++) for(int y = 0; y < gs->getMap().height; ++y) for(int x = 0; x < gs->getMap().width; ++x) { diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 8f9a9def4..a0a33f54e 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -239,8 +239,7 @@ void CMap::showObject(CGObjectInstance * obj) void CMap::calculateGuardingGreaturePositions() { - int levels = twoLevel ? 2 : 1; - for(int z = 0; z < levels; z++) + for(int z = 0; z < levels(); z++) { for(int x = 0; x < width; x++) { diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index dc8b5bf05..645187daa 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -370,7 +370,7 @@ inline bool CMap::isInTheMap(const int3 & pos) const return static_cast(pos.x) < static_cast(width) && static_cast(pos.y) < static_cast(height) && - static_cast(pos.z) <= (twoLevel ? 1 : 0); + static_cast(pos.z) <= mapLevels - 1; } inline TerrainTile & CMap::getTile(const int3 & tile) diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index 430e342a8..7fc258be3 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -122,7 +122,7 @@ CMapHeader::CMapHeader() : version(EMapFormat::VCMI) , height(72) , width(72) - , twoLevel(true) + , mapLevels(2) , difficulty(EMapDifficulty::NORMAL) , levelLimit(0) , victoryIconIndex(0) @@ -139,7 +139,7 @@ CMapHeader::~CMapHeader() = default; ui8 CMapHeader::levels() const { - return (twoLevel ? 2 : 1); + return mapLevels; } void CMapHeader::registerMapStrings() diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index 5391877b0..dbb8c0c71 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -246,7 +246,7 @@ public: si32 height; /// The default value is 72. si32 width; /// The default value is 72. - bool twoLevel; /// The default value is true. + si32 mapLevels; /// The default value is 2. MetaString name; MetaString description; EMapDifficulty difficulty; @@ -295,7 +295,23 @@ public: h & creationDateTime; h & width; h & height; - h & twoLevel; + if (h.version >= Handler::Version::MORE_MAP_LAYERS) + h & mapLevels; + else + { + if (h.saving) + { + bool hasTwoLevels = mapLevels == 2; + h & hasTwoLevels; + } + else + { + bool hasTwoLevels; + h & hasTwoLevels; + mapLevels = hasTwoLevels ? 2 : 1; + } + } + h & difficulty; h & levelLimit; diff --git a/lib/mapping/CMapOperation.cpp b/lib/mapping/CMapOperation.cpp index 7011a39c8..92d985bd6 100644 --- a/lib/mapping/CMapOperation.cpp +++ b/lib/mapping/CMapOperation.cpp @@ -563,14 +563,11 @@ CDrawTerrainOperation::ValidationResult::ValidationResult(bool result, std::stri CClearTerrainOperation::CClearTerrainOperation(CMap* map, vstd::RNG* gen) : CComposedOperation(map) { - CTerrainSelection terrainSel(map); - terrainSel.selectRange(MapRect(int3(0, 0, 0), map->width, map->height)); - addOperation(std::make_unique(map, terrainSel, ETerrainId::WATER, 0, gen)); - if(map->twoLevel) + for (int i = 0; i < map->mapLevels; i++) { - terrainSel.clearSelection(); - terrainSel.selectRange(MapRect(int3(0, 0, 1), map->width, map->height)); - addOperation(std::make_unique(map, terrainSel, ETerrainId::ROCK, 0, gen)); + CTerrainSelection terrainSel(map); + terrainSel.selectRange(MapRect(int3(0, 0, i), map->width, map->height)); + addOperation(std::make_unique(map, terrainSel, i == 1 ? ETerrainId::ROCK : ETerrainId::WATER, 0, gen)); } } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index c9a2923b8..a153d2a6f 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -185,7 +185,7 @@ void CMapLoaderH3M::readHeader() // Read map name, description, dimensions,... mapHeader->areAnyPlayers = reader->readBool(); mapHeader->height = mapHeader->width = reader->readInt32(); - mapHeader->twoLevel = reader->readBool(); + mapHeader->mapLevels = reader->readBool() ? 2 : 1; mapHeader->name.appendTextID(readLocalizedString("header.name")); mapHeader->description.appendTextID(readLocalizedString("header.description")); mapHeader->author.appendRawString(""); diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index a9e47ef66..ca84d3d6e 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -240,7 +240,16 @@ const int CMapFormatJson::VERSION_MINOR = 0; const std::string CMapFormatJson::HEADER_FILE_NAME = "header.json"; const std::string CMapFormatJson::OBJECTS_FILE_NAME = "objects.json"; -const std::string CMapFormatJson::TERRAIN_FILE_NAMES[2] = {"surface_terrain.json", "underground_terrain.json"}; + +std::string getTerrainFilename(int i) +{ + if(i == 0) + return "surface_terrain.json"; + else if(i == 1) + return "underground_terrain.json"; + else + return "level-" + std::to_string(i + 1) + "_terrain.json"; +} CMapFormatJson::CMapFormatJson(): fileVersionMajor(0), fileVersionMinor(0), @@ -844,7 +853,6 @@ void CMapLoaderJson::readHeader(const bool complete) //loading mods mapHeader->mods = ModVerificationInfo::jsonDeserializeList(header["mods"]); - //todo: multilevel map load support { auto levels = handler.enterStruct("mapLevels"); { @@ -852,10 +860,7 @@ void CMapLoaderJson::readHeader(const bool complete) handler.serializeInt("height", mapHeader->height); handler.serializeInt("width", mapHeader->width); } - { - auto underground = handler.enterStruct("underground"); - mapHeader->twoLevel = !underground->getCurrent().isNull(); - } + mapHeader->mapLevels = levels->getCurrent().Struct().size(); } serializeHeader(handler); @@ -993,14 +998,10 @@ void CMapLoaderJson::readTerrainLevel(const JsonNode & src, const int index) void CMapLoaderJson::readTerrain() { + for(int i = 0; i < map->mapLevels; i++) { - const JsonNode surface = getFromArchive(TERRAIN_FILE_NAMES[0]); - readTerrainLevel(surface, 0); - } - if(map->twoLevel) - { - const JsonNode underground = getFromArchive(TERRAIN_FILE_NAMES[1]); - readTerrainLevel(underground, 1); + const JsonNode node = getFromArchive(getTerrainFilename(i)); + readTerrainLevel(node, i); } } @@ -1198,17 +1199,22 @@ void CMapSaverJson::writeHeader() //write mods header["mods"] = ModVerificationInfo::jsonSerializeList(mapHeader->mods); - //todo: multilevel map save support - JsonNode & levels = header["mapLevels"]; - levels["surface"]["height"].Float() = mapHeader->height; - levels["surface"]["width"].Float() = mapHeader->width; - levels["surface"]["index"].Float() = 0; + auto getName = [](int level){ + if(level == 0) + return std::string("surface"); + else if(level == 1) + return std::string("underground"); + else + return "level-" + std::to_string(level + 1); + }; - if(mapHeader->twoLevel) + JsonNode & levels = header["mapLevels"]; + for(int i = 0; i < map->mapLevels; i++) { - levels["underground"]["height"].Float() = mapHeader->height; - levels["underground"]["width"].Float() = mapHeader->width; - levels["underground"]["index"].Float() = 1; + auto name = getName(i); + levels[name]["height"].Float() = mapHeader->height; + levels[name]["width"].Float() = mapHeader->width; + levels[name]["index"].Float() = i; } serializeHeader(handler); @@ -1266,15 +1272,11 @@ JsonNode CMapSaverJson::writeTerrainLevel(const int index) void CMapSaverJson::writeTerrain() { logGlobal->trace("Saving terrain"); - //todo: multilevel map save support - JsonNode surface = writeTerrainLevel(0); - addToArchive(surface, TERRAIN_FILE_NAMES[0]); - - if(map->twoLevel) + for(int i = 0; i < map->mapLevels; i++) { - JsonNode underground = writeTerrainLevel(1); - addToArchive(underground, TERRAIN_FILE_NAMES[1]); + JsonNode node = writeTerrainLevel(i); + addToArchive(node, getTerrainFilename(i)); } } diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index c27dda80c..e10460ae1 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -24,7 +24,7 @@ VCMI_LIB_NAMESPACE_BEGIN CMapGenOptions::CMapGenOptions() - : width(CMapHeader::MAP_SIZE_MIDDLE), height(CMapHeader::MAP_SIZE_MIDDLE), hasTwoLevels(true), + : width(CMapHeader::MAP_SIZE_MIDDLE), height(CMapHeader::MAP_SIZE_MIDDLE), levels(2), humanOrCpuPlayerCount(RANDOM_SIZE), teamCount(RANDOM_SIZE), compOnlyPlayerCount(RANDOM_SIZE), compOnlyTeamCount(RANDOM_SIZE), waterContent(EWaterContent::RANDOM), monsterStrength(EMonsterStrength::RANDOM), mapTemplate(nullptr), customizedPlayers(false) @@ -54,14 +54,14 @@ void CMapGenOptions::setHeight(si32 value) height = value; } -bool CMapGenOptions::getHasTwoLevels() const +int CMapGenOptions::getLevels() const { - return hasTwoLevels; + return levels; } -void CMapGenOptions::setHasTwoLevels(bool value) +void CMapGenOptions::setLevels(int value) { - hasTwoLevels = value; + levels = value; } si8 CMapGenOptions::getHumanOrCpuPlayerCount() const @@ -425,12 +425,12 @@ void CMapGenOptions::setMapTemplate(const CRmgTemplate * value) //validate & adapt options according to template if(mapTemplate) { - if(!mapTemplate->matchesSize(int3(getWidth(), getHeight(), 1 + getHasTwoLevels()))) + if(!mapTemplate->matchesSize(int3(getWidth(), getHeight(), getLevels()))) { auto sizes = mapTemplate->getMapSizes(); setWidth(sizes.first.x); setHeight(sizes.first.y); - setHasTwoLevels(sizes.first.z - 1); + setLevels(sizes.first.z); } si8 maxPlayerCount = getMaxPlayersCount(false); @@ -488,7 +488,7 @@ void CMapGenOptions::setPlayerTeam(const PlayerColor & color, const TeamID & tea void CMapGenOptions::finalize(vstd::RNG & rand) { - logGlobal->info("RMG map: %dx%d, %s underground", getWidth(), getHeight(), getHasTwoLevels() ? "WITH" : "NO"); + logGlobal->info("RMG map: %dx%d, %s underground", getWidth(), getHeight(), getLevels() >= 2 ? "WITH" : "NO"); logGlobal->info("RMG settings: players %d, teams %d, computer players %d, computer teams %d, water %d, monsters %d", static_cast(getHumanOrCpuPlayerCount()), static_cast(getTeamCount()), static_cast(getCompOnlyPlayerCount()), static_cast(getCompOnlyTeamCount()), static_cast(getWaterContent()), static_cast(getMonsterStrength())); @@ -700,7 +700,7 @@ bool CMapGenOptions::arePlayersCustomized() const std::vector CMapGenOptions::getPossibleTemplates() const { - int3 tplSize(width, height, (hasTwoLevels ? 2 : 1)); + int3 tplSize(width, height, levels); auto humanPlayers = countHumanPlayers(); auto templates = LIBRARY->tplh->getTemplates(); @@ -825,7 +825,12 @@ void CMapGenOptions::serializeJson(JsonSerializeFormat & handler) { handler.serializeInt("width", width); handler.serializeInt("height", height); - handler.serializeBool("haswoLevels", hasTwoLevels); + bool hasTwoLevelsKey = !handler.getCurrent()["haswoLevels"].isNull(); + bool hasTwoLevels = levels == 2; + if(handler.saving || !hasTwoLevelsKey) + handler.serializeInt("levels", levels); + else + handler.serializeBool("haswoLevels", hasTwoLevels); handler.serializeInt("humanOrCpuPlayerCount", humanOrCpuPlayerCount); handler.serializeInt("teamCount", teamCount); handler.serializeInt("compOnlyPlayerCount", compOnlyPlayerCount); diff --git a/lib/rmg/CMapGenOptions.h b/lib/rmg/CMapGenOptions.h index 3f151c435..98a1cbea7 100644 --- a/lib/rmg/CMapGenOptions.h +++ b/lib/rmg/CMapGenOptions.h @@ -90,8 +90,8 @@ public: si32 getHeight() const; void setHeight(si32 value); - bool getHasTwoLevels() const; - void setHasTwoLevels(bool value); + int getLevels() const; + void setLevels(int value); /// The count of all (human or computer) players ranging from 1 to PlayerColor::PLAYER_LIMIT or RANDOM_SIZE for random. If you call /// this method, all player settings are reset to default settings. @@ -170,7 +170,7 @@ private: si32 width; si32 height; - bool hasTwoLevels; + si32 levels; si8 humanOrCpuPlayerCount; si8 teamCount; si8 compOnlyPlayerCount; @@ -190,7 +190,22 @@ public: { h & width; h & height; - h & hasTwoLevels; + if (h.version >= Handler::Version::MORE_MAP_LAYERS) + h & levels; + else + { + if (h.saving) + { + bool hasTwoLevels = levels == 2; + h & hasTwoLevels; + } + else + { + bool hasTwoLevels; + h & hasTwoLevels; + levels = hasTwoLevels ? 2 : 1; + } + } h & humanOrCpuPlayerCount; h & teamCount; h & compOnlyPlayerCount; diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 7c1fcb1b7..71b09a167 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -458,7 +458,7 @@ void CMapGenerator::addHeaderInfo() m.version = EMapFormat::VCMI; m.width = mapGenOptions.getWidth(); m.height = mapGenOptions.getHeight(); - m.twoLevel = mapGenOptions.getHasTwoLevels(); + m.mapLevels = mapGenOptions.getLevels(); m.name.appendLocalString(EMetaText::GENERAL_TXT, 740); m.description = getMapDescription(); m.difficulty = EMapDifficulty::NORMAL; diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 6b0c9f3be..682ff331f 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -329,7 +329,7 @@ void CZonePlacer::placeZones(vstd::RNG * rand) { return pr.second->getType() == ETemplateZoneType::WATER; }); - bool underground = map.getMapGenOptions().getHasTwoLevels(); + bool mapLevels = map.getMapGenOptions().getLevels(); findPathsBetweenZones(); placeOnGrid(rand); @@ -347,7 +347,7 @@ void CZonePlacer::placeZones(vstd::RNG * rand) RandomGeneratorUtil::randomShuffle(zonesVector, *rand); //0. set zone sizes and surface / underground level - prepareZones(zones, zonesVector, underground, rand); + prepareZones(zones, zonesVector, mapLevels, rand); std::map, float3> bestSolution; @@ -441,21 +441,44 @@ void CZonePlacer::placeZones(vstd::RNG * rand) } } -void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, vstd::RNG * rand) +void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const int mapLevels, vstd::RNG * rand) { - std::vector totalSize = { 0, 0 }; //make sure that sum of zone sizes on surface and uderground match size of the map + std::map totalSize; //make sure that sum of zone sizes on surface and uderground match size of the map - int zonesOnLevel[2] = { 0, 0 }; + std::map zonesOnLevel; + for (int i = 0; i < mapLevels; i++) + zonesOnLevel[i] = 0; //even distribution for surface / underground zones. Surface zones always have priority. TZoneVector zonesToPlace; std::map levels; + auto addZoneEqually = [&](auto & zone, bool ignoreUnderground = false) { + int chosenLevel = -1; + int minCount = std::numeric_limits::max(); + + for (const auto& [level, count] : zonesOnLevel) { + if (ignoreUnderground && level == 1) + continue; + + if (count < minCount || + (count == minCount && level == 0) || + (count == minCount && chosenLevel != 0 && level < chosenLevel)) + { + chosenLevel = level; + minCount = count; + } + } + + levels[zone.first] = chosenLevel; + zonesOnLevel[chosenLevel]++; + }; + //first pass - determine fixed surface for zones for(const auto & zone : zonesVector) { - if (!underground) //this step is ignored + if (mapLevels == 1) //this step is ignored zonesToPlace.push_back(zone); else //place players depending on their factions { @@ -495,8 +518,7 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const else { //surface - zonesOnLevel[0]++; - levels[zone.first] = 0; + addZoneEqually(zone, true); } } } @@ -509,17 +531,8 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const } for(const auto & zone : zonesToPlace) { - if (underground) //only then consider underground zones - { - int level = 0; - if (zonesOnLevel[1] < zonesOnLevel[0]) //only if there are less underground zones - level = 1; - else - level = 0; - - levels[zone.first] = level; - zonesOnLevel[level]++; - } + if (mapLevels > 1) //only then consider underground zones + addZoneEqually(zone); else levels[zone.first] = 0; } @@ -541,8 +554,8 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const prescaler = sqrt((WH)/(sum(n^2)*pi)) */ - std::vector prescaler = { 0, 0 }; - for (int i = 0; i < 2; i++) + std::map prescaler; + for (int i = 0; i < mapLevels; i++) prescaler[i] = std::sqrt((width * height) / (totalSize[i] * PI_CONSTANT)); mapSize = static_cast(sqrt(width * height)); for(const auto & zone : zones) diff --git a/lib/rmg/CZonePlacer.h b/lib/rmg/CZonePlacer.h index 43e3479df..33b36ec0e 100644 --- a/lib/rmg/CZonePlacer.h +++ b/lib/rmg/CZonePlacer.h @@ -50,7 +50,7 @@ public: const TDistanceMap & getDistanceMap(); private: - void prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, vstd::RNG * rand); + void prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const int mapLevels, vstd::RNG * rand); void attractConnectedZones(TZoneMap & zones, TForceVector & forces, TDistanceVector & distances) const; void separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps); void moveOneZone(TZoneMap & zones, TForceVector & totalForces, TDistanceVector & distances, TDistanceVector & overlaps); diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 7221e1f3d..052a8e653 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -48,8 +48,9 @@ enum class ESerializationVersion : int32_t UNIVERSITY_CONFIG, // town university is configurable CAMPAIGN_BONUSES, // new format for scenario bonuses in campaigns BONUS_HIDDEN, // hidden bonus + MORE_MAP_LAYERS, // more map layers - CURRENT = BONUS_HIDDEN, + CURRENT = MORE_MAP_LAYERS, }; static_assert(ESerializationVersion::MINIMAL <= ESerializationVersion::CURRENT, "Invalid serialization version definition!"); diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 8804acead..8fd9a86d4 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -387,7 +387,7 @@ void MainWindow::initializeMap(bool isNew) ui->actionMapSettings->setEnabled(true); ui->actionPlayers_settings->setEnabled(true); ui->actionTranslations->setEnabled(true); - ui->actionLevel->setEnabled(controller.map()->twoLevel); + ui->actionLevel->setEnabled(controller.map()->mapLevels == 2); // TODO: multilevel support //set minimal players count if(isNew) @@ -968,7 +968,7 @@ void MainWindow::loadObjectsTree() void MainWindow::on_actionLevel_triggered() { - if(controller.map() && controller.map()->twoLevel) + if(controller.map() && controller.map()->mapLevels == 2) // TODO: multilevel support { mapLevel = mapLevel ? 0 : 1; ui->mapView->setScene(controller.scene(mapLevel)); diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index d6ca9e702..8aecb33a3 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -260,7 +260,7 @@ void MapController::sceneForceUpdate() { _scenes[0]->updateViews(); _miniscenes[0]->updateViews(); - if(_map->twoLevel) + if(_map->mapLevels == 2) // TODO: multilevel support { _scenes[1]->updateViews(); _miniscenes[1]->updateViews(); diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp index 651557da4..323296eb5 100644 --- a/mapeditor/maphandler.cpp +++ b/mapeditor/maphandler.cpp @@ -256,7 +256,7 @@ void MapHandler::initObjectRects() if(!map) return; - tileObjects.resize(map->width * map->height * (map->twoLevel ? 2 : 1)); + tileObjects.resize(map->width * map->height * map->mapLevels); //initializing objects / rects for(const auto & elem : map->objects) diff --git a/mapeditor/scenelayer.cpp b/mapeditor/scenelayer.cpp index 14a76d0bf..3eb68e795 100644 --- a/mapeditor/scenelayer.cpp +++ b/mapeditor/scenelayer.cpp @@ -93,7 +93,7 @@ void PassabilityLayer::update() pixmap.reset(new QPixmap(map->width * 32, map->height * 32)); pixmap->fill(Qt::transparent); - if(scene->level == 0 || map->twoLevel) + if(scene->level == 0 || map->mapLevels == 2) // TODO: multilevel support { QPainter painter(pixmap.get()); for(int j = 0; j < map->height; ++j) @@ -121,7 +121,7 @@ void ObjectPickerLayer::highlight(std::function if(!map) return; - if(scene->level == 0 || map->twoLevel) + if(scene->level == 0 || map->mapLevels == 2) // TODO: multilevel support { for(int j = 0; j < map->height; ++j) { diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index 22c6683f2..11048d3da 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -78,7 +78,7 @@ WindowNewMap::WindowNewMap(QWidget *parent) : mapGenOptions.setWidth(width ? width : 1); mapGenOptions.setHeight(height ? height : 1); bool twoLevel = ui->twoLevelCheck->isChecked(); - mapGenOptions.setHasTwoLevels(twoLevel); + mapGenOptions.setLevels(twoLevel ? 2 : 1); // TODO: multilevel support updateTemplateList(); } @@ -123,7 +123,7 @@ bool WindowNewMap::loadUserSettings() } } - ui->twoLevelCheck->setChecked(mapGenOptions.getHasTwoLevels()); + ui->twoLevelCheck->setChecked(mapGenOptions.getLevels() == 2); // TODO: multilevel support ui->humanCombo->setCurrentIndex(mapGenOptions.getHumanOrCpuPlayerCount()); ui->cpuCombo->setCurrentIndex(mapGenOptions.getCompOnlyPlayerCount()); @@ -213,7 +213,7 @@ std::unique_ptr generateEmptyMap(CMapGenOptions & options) map->creationDateTime = std::time(nullptr); map->width = options.getWidth(); map->height = options.getHeight(); - map->twoLevel = options.getHasTwoLevels(); + map->mapLevels = options.getLevels(); map->initTerrain(); map->getEditManager()->clearTerrain(&CRandomGenerator::getDefault()); @@ -331,7 +331,7 @@ void WindowNewMap::on_sizeCombo_activated(int index) void WindowNewMap::on_twoLevelCheck_stateChanged(int arg1) { bool twoLevel = ui->twoLevelCheck->isChecked(); - mapGenOptions.setHasTwoLevels(twoLevel); + mapGenOptions.setLevels(twoLevel ? 2 : 1); // TODO: multilevel support updateTemplateList(); } diff --git a/test/map/CMapFormatTest.cpp b/test/map/CMapFormatTest.cpp index 21dec61b0..47544fd07 100644 --- a/test/map/CMapFormatTest.cpp +++ b/test/map/CMapFormatTest.cpp @@ -52,7 +52,7 @@ TEST(MapFormat, DISABLED_Random) opt.setHeight(CMapHeader::MAP_SIZE_MIDDLE); opt.setWidth(CMapHeader::MAP_SIZE_MIDDLE); - opt.setHasTwoLevels(true); + opt.setLevels(2); opt.setHumanOrCpuPlayerCount(4); opt.setPlayerTypeForStandardPlayer(PlayerColor(0), EPlayerType::HUMAN); diff --git a/test/map/MapComparer.cpp b/test/map/MapComparer.cpp index cef5c6ab0..e1c51b0c3 100644 --- a/test/map/MapComparer.cpp +++ b/test/map/MapComparer.cpp @@ -151,7 +151,7 @@ void MapComparer::compareHeader() //map size parameters are vital for further checks VCMI_REQUIRE_FIELD_EQUAL_P(height); VCMI_REQUIRE_FIELD_EQUAL_P(width); - VCMI_REQUIRE_FIELD_EQUAL_P(twoLevel); + VCMI_REQUIRE_FIELD_EQUAL_P(mapLevels); VCMI_CHECK_FIELD_EQUAL_P(name); VCMI_CHECK_FIELD_EQUAL_P(description); From 4552c1f562891c4be924e3bf56af9975393a16ca Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 2 Aug 2025 02:15:05 +0200 Subject: [PATCH 2/4] map editor multi level support --- mapeditor/mainwindow.cpp | 83 +++++++++++++++++++++++++++---------- mapeditor/mainwindow.h | 5 ++- mapeditor/mapcontroller.cpp | 20 ++++----- mapeditor/mapcontroller.h | 6 ++- mapeditor/windownewmap.cpp | 13 +++--- mapeditor/windownewmap.h | 2 +- mapeditor/windownewmap.ui | 44 +++++++++++++------- 7 files changed, 115 insertions(+), 58 deletions(-) diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 8fd9a86d4..cdbda068a 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -247,7 +247,6 @@ MainWindow::MainWindow(QWidget* parent) : ui->menuOpenRecent->setIcon(QIcon{":/icons/document-open-recent.png"}); ui->actionSave->setIcon(QIcon{":/icons/document-save.png"}); ui->actionNew->setIcon(QIcon{":/icons/document-new.png"}); - ui->actionLevel->setIcon(QIcon{":/icons/toggle-underground.png"}); ui->actionPass->setIcon(QIcon{":/icons/toggle-pass.png"}); ui->actionCut->setIcon(QIcon{":/icons/edit-cut.png"}); ui->actionCopy->setIcon(QIcon{":/icons/edit-copy.png"}); @@ -266,6 +265,54 @@ MainWindow::MainWindow(QWidget* parent) : ui->actionCampaignEditor->setIcon(QIcon{":/icons/mapeditor.64x64.png"}); ui->actionTemplateEditor->setIcon(QIcon{":/icons/dice.png"}); + // Add combobox action + for (QWidget* c : QList{ ui->toolBar, ui->menuView }) + { + QWidget* container = new QWidget; + QHBoxLayout* layout = new QHBoxLayout(container); + layout->setContentsMargins(6, 2, 4, 2); + layout->setSpacing(2); + + if (c == ui->menuView) + { + // Add icon label only for QMenu + QLabel* iconLabel = new QLabel; + iconLabel->setPixmap(QIcon(":/icons/toggle-underground.png").pixmap(16, 16)); + iconLabel->setContentsMargins(0, 2, 0, 0); + layout->addWidget(iconLabel); + } + + // Add the combo box + QComboBox* combo = new QComboBox; + combo->setFixedHeight(ui->menuView->fontMetrics().height() + 6); + connect(combo, QOverload::of(&QComboBox::currentIndexChanged), this, [this, combo](int index) { + for(auto & box : levelComboBoxes) + if (box->currentIndex() != index && combo != box) + box->setCurrentIndex(index); + + if(!controller.map()) + return; + + mapLevel = combo->currentIndex(); + ui->mapView->setScene(controller.scene(mapLevel)); + ui->minimapView->setScene(controller.miniScene(mapLevel)); + }); + layout->addWidget(combo, c == ui->menuView ? 1 : 0); + + // Create the widget action + QWidgetAction* comboAction = new QWidgetAction(this); + comboAction->setDefaultWidget(container); + + int index = c->actions().indexOf(ui->actionLevel); + if (index != -1) + { + c->removeAction(ui->actionLevel); + c->insertAction(c->actions().value(index), comboAction); + } + + levelComboBoxes.push_back(combo); + } + #ifndef ENABLE_TEMPLATE_EDITOR ui->actionTemplateEditor->setVisible(false); #endif @@ -387,7 +434,19 @@ void MainWindow::initializeMap(bool isNew) ui->actionMapSettings->setEnabled(true); ui->actionPlayers_settings->setEnabled(true); ui->actionTranslations->setEnabled(true); - ui->actionLevel->setEnabled(controller.map()->mapLevels == 2); // TODO: multilevel support + for(auto & box : levelComboBoxes) + { + box->clear(); + for(int i = 0; i < controller.map()->mapLevels; i++) + { + if(i == 0) + box->addItems({ tr("Surface") }); + else if(i == 1) + box->addItems({ tr("Underground") }); + else + box->addItems({ tr("Level - %1").arg(i + 1) }); + } + } //set minimal players count if(isNew) @@ -966,26 +1025,6 @@ void MainWindow::loadObjectsTree() } } -void MainWindow::on_actionLevel_triggered() -{ - if(controller.map() && controller.map()->mapLevels == 2) // TODO: multilevel support - { - mapLevel = mapLevel ? 0 : 1; - ui->mapView->setScene(controller.scene(mapLevel)); - ui->minimapView->setScene(controller.miniScene(mapLevel)); - if (mapLevel == 0) - { - ui->actionLevel->setText(tr("View underground")); - ui->actionLevel->setToolTip(tr("View underground")); - } - else - { - ui->actionLevel->setText(tr("View surface")); - ui->actionLevel->setToolTip(tr("View surface")); - } - } -} - void MainWindow::on_actionUndo_triggered() { QString str(tr("Undo clicked")); diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index a734e8843..06b321479 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "mapcontroller.h" #include "resourceExtractor/ResourceConverter.h" @@ -84,8 +85,6 @@ private slots: void on_actionNew_triggered(); - void on_actionLevel_triggered(); - void on_actionSave_triggered(); void on_actionErase_triggered(); @@ -196,6 +195,8 @@ private: ObjectBrowserProxyModel * objectBrowser = nullptr; QGraphicsScene * scenePreview; MapSettings * mapSettings = nullptr; + + QList levelComboBoxes; QString filename; QString lastSavingDir; diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 8aecb33a3..40fe6497e 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -45,7 +45,7 @@ MapController::MapController(QObject * parent) MapController::MapController(MainWindow * m): main(m) { - for(int i : {0, 1}) + for(int i = 0; i < MAX_LEVELS; i++) { _scenes[i].reset(new MapScene(i)); _miniscenes[i].reset(new MinimapScene(i)); @@ -56,12 +56,12 @@ MapController::MapController(MainWindow * m): main(m) void MapController::connectScenes() { - for (int level = 0; level <= 1; level++) + for(int i = 0; i < MAX_LEVELS; i++) { //selections for both layers will be handled separately - QObject::connect(_scenes[level].get(), &MapScene::selected, [this, level](bool anythingSelected) + QObject::connect(_scenes[i].get(), &MapScene::selected, [this, i](bool anythingSelected) { - main->onSelectionMade(level, anythingSelected); + main->onSelectionMade(i, anythingSelected); }); } } @@ -223,7 +223,7 @@ void MapController::setMap(std::unique_ptr cmap) repairMap(); - for(int i : {0, 1}) + for(int i = 0; i < _map->mapLevels; i++) { _scenes[i].reset(new MapScene(i)); _miniscenes[i].reset(new MinimapScene(i)); @@ -258,12 +258,10 @@ void MapController::initObstaclePainters(CMap * map) void MapController::sceneForceUpdate() { - _scenes[0]->updateViews(); - _miniscenes[0]->updateViews(); - if(_map->mapLevels == 2) // TODO: multilevel support + for(int i = 0; i < _map->mapLevels; i++) { - _scenes[1]->updateViews(); - _miniscenes[1]->updateViews(); + _scenes[i]->updateViews(); + _miniscenes[i]->updateViews(); } } @@ -278,7 +276,7 @@ void MapController::resetMapHandler() if(!_mapHandler) _mapHandler.reset(new MapHandler()); _mapHandler->reset(map()); - for(int i : {0, 1}) + for(int i = 0; i < MAX_LEVELS; i++) { _scenes[i]->initialize(*this); _miniscenes[i]->initialize(*this); diff --git a/mapeditor/mapcontroller.h b/mapeditor/mapcontroller.h index 6ed3d90c3..cab62a57b 100644 --- a/mapeditor/mapcontroller.h +++ b/mapeditor/mapcontroller.h @@ -100,11 +100,13 @@ private: std::unique_ptr _map; std::unique_ptr _mapHandler; MainWindow * main; - mutable std::array, 2> _scenes; - mutable std::array, 2> _miniscenes; + mutable std::map> _scenes; + mutable std::map> _miniscenes; std::vector> _clipboard; int _clipboardShiftIndex = 0; + const int MAX_LEVELS = 10; + std::map> _obstaclePainters; void connectScenes(); diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index 11048d3da..4e5efcc56 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -77,8 +77,7 @@ WindowNewMap::WindowNewMap(QWidget *parent) : int height = ui->heightTxt->text().toInt(); mapGenOptions.setWidth(width ? width : 1); mapGenOptions.setHeight(height ? height : 1); - bool twoLevel = ui->twoLevelCheck->isChecked(); - mapGenOptions.setLevels(twoLevel ? 2 : 1); // TODO: multilevel support + mapGenOptions.setLevels(ui->spinBoxLevels->value()); updateTemplateList(); } @@ -123,7 +122,7 @@ bool WindowNewMap::loadUserSettings() } } - ui->twoLevelCheck->setChecked(mapGenOptions.getLevels() == 2); // TODO: multilevel support + ui->spinBoxLevels->setValue(mapGenOptions.getLevels()); ui->humanCombo->setCurrentIndex(mapGenOptions.getHumanOrCpuPlayerCount()); ui->cpuCombo->setCurrentIndex(mapGenOptions.getCompOnlyPlayerCount()); @@ -328,10 +327,12 @@ void WindowNewMap::on_sizeCombo_activated(int index) } -void WindowNewMap::on_twoLevelCheck_stateChanged(int arg1) +void WindowNewMap::on_spinBoxLevels_valueChanged(int value) { - bool twoLevel = ui->twoLevelCheck->isChecked(); - mapGenOptions.setLevels(twoLevel ? 2 : 1); // TODO: multilevel support + if(value > 2) + QMessageBox::warning(this, tr("Multilevel support"), tr("Multilevel support is highly experimental yet. Expect issues.")); // TODO: multilevel support + + mapGenOptions.setLevels(ui->spinBoxLevels->value()); updateTemplateList(); } diff --git a/mapeditor/windownewmap.h b/mapeditor/windownewmap.h index a3695a0f8..1303f0e3c 100644 --- a/mapeditor/windownewmap.h +++ b/mapeditor/windownewmap.h @@ -87,7 +87,7 @@ private slots: void on_sizeCombo_activated(int index); - void on_twoLevelCheck_stateChanged(int arg1); + void on_spinBoxLevels_valueChanged(int value); void on_humanCombo_activated(int index); diff --git a/mapeditor/windownewmap.ui b/mapeditor/windownewmap.ui index 6462aa850..af2bec085 100644 --- a/mapeditor/windownewmap.ui +++ b/mapeditor/windownewmap.ui @@ -270,24 +270,40 @@ - + - 10 - 90 - 277 - 20 + 20 + 86 + 191 + 29 - - - 96 - 0 - - - - Underground - + + + + + Levels + + + + + + + + + + + + + + + + 1 + + + + From d2d0b2292e17a11f381a25ea0253d3d5ba8d179f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 2 Aug 2025 02:33:47 +0200 Subject: [PATCH 3/4] ui support --- client/adventureMap/AdventureMapShortcuts.cpp | 4 -- client/mapView/MapView.cpp | 5 ++- mapeditor/scenelayer.cpp | 42 ++++++++----------- 3 files changed, 21 insertions(+), 30 deletions(-) diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index ed4923bb9..b6d3618cc 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -153,10 +153,6 @@ void AdventureMapShortcuts::worldViewScale4x() void AdventureMapShortcuts::switchMapLevel() { - int maxLevels = GAME->interface()->cb->getMapSize().z; - if (maxLevels < 2) // TODO: multilevel support - return; - owner.hotkeySwitchMapLevel(); } diff --git a/client/mapView/MapView.cpp b/client/mapView/MapView.cpp index 623e45e02..9ee7095bc 100644 --- a/client/mapView/MapView.cpp +++ b/client/mapView/MapView.cpp @@ -116,8 +116,9 @@ void MapView::onMapLevelSwitched() { if(GAME->interface()->cb->getMapSize().z > 1) { - if(model->getLevel() == 0) - controller->setViewCenter(model->getMapViewCenter(), 1); + int newLevel = model->getLevel() + 1; + if(newLevel < GAME->interface()->cb->getMapSize().z) + controller->setViewCenter(model->getMapViewCenter(), newLevel); else controller->setViewCenter(model->getMapViewCenter(), 0); } diff --git a/mapeditor/scenelayer.cpp b/mapeditor/scenelayer.cpp index 3eb68e795..7f34be7ff 100644 --- a/mapeditor/scenelayer.cpp +++ b/mapeditor/scenelayer.cpp @@ -93,18 +93,15 @@ void PassabilityLayer::update() pixmap.reset(new QPixmap(map->width * 32, map->height * 32)); pixmap->fill(Qt::transparent); - if(scene->level == 0 || map->mapLevels == 2) // TODO: multilevel support + QPainter painter(pixmap.get()); + for(int j = 0; j < map->height; ++j) { - QPainter painter(pixmap.get()); - for(int j = 0; j < map->height; ++j) + for(int i = 0; i < map->width; ++i) { - for(int i = 0; i < map->width; ++i) + auto tl = map->getTile(int3(i, j, scene->level)); + if(tl.blocked() || tl.visitable()) { - auto tl = map->getTile(int3(i, j, scene->level)); - if(tl.blocked() || tl.visitable()) - { - painter.fillRect(i * 32, j * 32, 31, 31, tl.visitable() ? QColor(200, 200, 0, 64) : QColor(255, 0, 0, 64)); - } + painter.fillRect(i * 32, j * 32, 31, 31, tl.visitable() ? QColor(200, 200, 0, 64) : QColor(255, 0, 0, 64)); } } } @@ -121,24 +118,21 @@ void ObjectPickerLayer::highlight(std::function if(!map) return; - if(scene->level == 0 || map->mapLevels == 2) // TODO: multilevel support + for(int j = 0; j < map->height; ++j) { - for(int j = 0; j < map->height; ++j) + for(int i = 0; i < map->width; ++i) { - for(int i = 0; i < map->width; ++i) - { - auto tl = map->getTile(int3(i, j, scene->level)); - ObjectInstanceID objID = tl.topVisitableObj(); - if(!objID.hasValue() && !tl.blockingObjects.empty()) - objID = tl.blockingObjects.front(); + auto tl = map->getTile(int3(i, j, scene->level)); + ObjectInstanceID objID = tl.topVisitableObj(); + if(!objID.hasValue() && !tl.blockingObjects.empty()) + objID = tl.blockingObjects.front(); - if (objID.hasValue()) - { - const CGObjectInstance * obj = map->getObject(objID); - - if(obj && predicate(obj)) - possibleObjects.insert(obj); - } + if (objID.hasValue()) + { + const CGObjectInstance * obj = map->getObject(objID); + + if(obj && predicate(obj)) + possibleObjects.insert(obj); } } } From dfd13fc49851be536f5a67f603108094df45aeb6 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 10 Aug 2025 17:24:52 +0200 Subject: [PATCH 4/4] Update mapeditor/mapcontroller.h Co-authored-by: Ivan Savenko --- mapeditor/mapcontroller.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapeditor/mapcontroller.h b/mapeditor/mapcontroller.h index cab62a57b..38c92ab65 100644 --- a/mapeditor/mapcontroller.h +++ b/mapeditor/mapcontroller.h @@ -105,7 +105,7 @@ private: std::vector> _clipboard; int _clipboardShiftIndex = 0; - const int MAX_LEVELS = 10; + const int MAX_LEVELS = 10; // TODO: multilevel support: remove this constant std::map> _obstaclePainters;