1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-09-16 09:26:28 +02:00

Merge pull request #5964 from Laserlicht/levels

multilevel support
This commit is contained in:
Ivan Savenko
2025-08-14 11:03:42 +03:00
committed by GitHub
32 changed files with 283 additions and 186 deletions

View File

@@ -153,10 +153,6 @@ void AdventureMapShortcuts::worldViewScale4x()
void AdventureMapShortcuts::switchMapLevel()
{
int maxLevels = GAME->interface()->cb->getMapSize().z;
if (maxLevels < 2)
return;
owner.hotkeySwitchMapLevel();
}

View File

@@ -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")

View File

@@ -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<CMapGenOptions> opts)
if(auto button = std::dynamic_pointer_cast<CToggleButton>(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<CMapGenOptions> 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<CToggleGroup>("groupMaxPlayers"))

View File

@@ -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);
}

View File

@@ -121,7 +121,7 @@ std::vector<std::shared_ptr<CanvasImage>> CMapOverviewWidget::createMinimaps(std
{
std::vector<std::shared_ptr<CanvasImage>> 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<std::shared_ptr<CanvasImage>> CMapOverviewWidget::createMinimaps(std
std::shared_ptr<CPicture> CMapOverviewWidget::buildDrawMinimap(const JsonNode & config) const
{
// TODO: multilevel support
logGlobal->debug("Building widget drawMinimap");
auto rect = readRect(config["rect"]);

View File

@@ -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;

View File

@@ -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<SpellID> & out, std::optional<ui16> level)

View File

@@ -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)
{

View File

@@ -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++)
{

View File

@@ -370,7 +370,7 @@ inline bool CMap::isInTheMap(const int3 & pos) const
return
static_cast<uint32_t>(pos.x) < static_cast<uint32_t>(width) &&
static_cast<uint32_t>(pos.y) < static_cast<uint32_t>(height) &&
static_cast<uint32_t>(pos.z) <= (twoLevel ? 1 : 0);
static_cast<uint32_t>(pos.z) <= mapLevels - 1;
}
inline TerrainTile & CMap::getTile(const int3 & tile)

View File

@@ -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()

View File

@@ -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;

View File

@@ -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<CDrawTerrainOperation>(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<CDrawTerrainOperation>(map, terrainSel, ETerrainId::ROCK, 0, gen));
CTerrainSelection terrainSel(map);
terrainSel.selectRange(MapRect(int3(0, 0, i), map->width, map->height));
addOperation(std::make_unique<CDrawTerrainOperation>(map, terrainSel, i == 1 ? ETerrainId::ROCK : ETerrainId::WATER, 0, gen));
}
}

View File

@@ -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("");

View File

@@ -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));
}
}

View File

@@ -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<int>(getHumanOrCpuPlayerCount()), static_cast<int>(getTeamCount()), static_cast<int>(getCompOnlyPlayerCount()),
static_cast<int>(getCompOnlyTeamCount()), static_cast<int>(getWaterContent()), static_cast<int>(getMonsterStrength()));
@@ -700,7 +700,7 @@ bool CMapGenOptions::arePlayersCustomized() const
std::vector<const CRmgTemplate *> 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);

View File

@@ -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;

View File

@@ -463,7 +463,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;

View File

@@ -330,7 +330,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);
@@ -348,7 +348,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<std::shared_ptr<Zone>, float3> bestSolution;
@@ -442,21 +442,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<float> totalSize = { 0, 0 }; //make sure that sum of zone sizes on surface and uderground match size of the map
std::map<int, float> totalSize; //make sure that sum of zone sizes on surface and uderground match size of the map
int zonesOnLevel[2] = { 0, 0 };
std::map<int, int> 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<TRmgTemplateZoneId, int> levels;
auto addZoneEqually = [&](auto & zone, bool ignoreUnderground = false) {
int chosenLevel = -1;
int minCount = std::numeric_limits<int>::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
{
@@ -496,8 +519,7 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const
else
{
//surface
zonesOnLevel[0]++;
levels[zone.first] = 0;
addZoneEqually(zone, true);
}
}
}
@@ -510,17 +532,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;
}
@@ -542,8 +555,8 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const
prescaler = sqrt((WH)/(sum(n^2)*pi))
*/
std::vector<float> prescaler = { 0, 0 };
for (int i = 0; i < 2; i++)
std::map<int, float> prescaler;
for (int i = 0; i < mapLevels; i++)
prescaler[i] = std::sqrt((width * height) / (totalSize[i] * PI_CONSTANT));
mapSize = static_cast<float>(sqrt(width * height));
for(const auto & zone : zones)

View File

@@ -51,7 +51,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);

View File

@@ -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!");

View File

@@ -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<QWidget*>{ 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<int>::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()->twoLevel);
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()->twoLevel)
{
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"));

View File

@@ -5,6 +5,7 @@
#include <QStandardItemModel>
#include <QTranslator>
#include <QTableWidgetItem>
#include <QComboBox>
#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();
@@ -197,6 +196,8 @@ private:
QGraphicsScene * scenePreview;
MapSettings * mapSettings = nullptr;
QList<QComboBox*> levelComboBoxes;
QString filename;
QString lastSavingDir;
bool unsaved = false;

View File

@@ -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> 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->twoLevel)
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);

View File

@@ -100,11 +100,13 @@ private:
std::unique_ptr<CMap> _map;
std::unique_ptr<MapHandler> _mapHandler;
MainWindow * main;
mutable std::array<std::unique_ptr<MapScene>, 2> _scenes;
mutable std::array<std::unique_ptr<MinimapScene>, 2> _miniscenes;
mutable std::map<int, std::unique_ptr<MapScene>> _scenes;
mutable std::map<int, std::unique_ptr<MinimapScene>> _miniscenes;
std::vector<std::unique_ptr<CGObjectInstance>> _clipboard;
int _clipboardShiftIndex = 0;
const int MAX_LEVELS = 10; // TODO: multilevel support: remove this constant
std::map<TerrainId, std::unique_ptr<EditorObstaclePlacer>> _obstaclePainters;
void connectScenes();

View File

@@ -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)

View File

@@ -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->twoLevel)
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<bool(const CGObjectInstance *)>
if(!map)
return;
if(scene->level == 0 || map->twoLevel)
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();
if (objID.hasValue())
{
auto tl = map->getTile(int3(i, j, scene->level));
ObjectInstanceID objID = tl.topVisitableObj();
if(!objID.hasValue() && !tl.blockingObjects.empty())
objID = tl.blockingObjects.front();
const CGObjectInstance * obj = map->getObject(objID);
if (objID.hasValue())
{
const CGObjectInstance * obj = map->getObject(objID);
if(obj && predicate(obj))
possibleObjects.insert(obj);
}
if(obj && predicate(obj))
possibleObjects.insert(obj);
}
}
}

View File

@@ -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.setHasTwoLevels(twoLevel);
mapGenOptions.setLevels(ui->spinBoxLevels->value());
updateTemplateList();
}
@@ -123,7 +122,7 @@ bool WindowNewMap::loadUserSettings()
}
}
ui->twoLevelCheck->setChecked(mapGenOptions.getHasTwoLevels());
ui->spinBoxLevels->setValue(mapGenOptions.getLevels());
ui->humanCombo->setCurrentIndex(mapGenOptions.getHumanOrCpuPlayerCount());
ui->cpuCombo->setCurrentIndex(mapGenOptions.getCompOnlyPlayerCount());
@@ -213,7 +212,7 @@ std::unique_ptr<CMap> 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());
@@ -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.setHasTwoLevels(twoLevel);
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();
}

View File

@@ -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);

View File

@@ -270,24 +270,40 @@
</item>
</layout>
</widget>
<widget class="QCheckBox" name="twoLevelCheck">
<widget class="QWidget" name="horizontalLayoutWidget_3">
<property name="geometry">
<rect>
<x>10</x>
<y>90</y>
<width>277</width>
<height>20</height>
<x>20</x>
<y>86</y>
<width>191</width>
<height>29</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>96</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Underground</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>Levels</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBoxLevels">
<property name="specialValueText">
<string/>
</property>
<property name="suffix">
<string/>
</property>
<property name="prefix">
<string/>
</property>
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QGroupBox" name="randomOptions">

View File

@@ -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);

View File

@@ -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);