diff --git a/Mods/vcmi/Content/config/rmg/symmetric/6lm10a.json b/Mods/vcmi/Content/config/rmg/symmetric/6lm10a.json index e135682ad..da7f9bd8d 100644 --- a/Mods/vcmi/Content/config/rmg/symmetric/6lm10a.json +++ b/Mods/vcmi/Content/config/rmg/symmetric/6lm10a.json @@ -290,7 +290,7 @@ { "a" : "16", "b" : "25", "guard" : 6000, "road" : "random" }, { "a" : "9", "b" : "10", "guard" : 6000, "road" : "true" }, - { "a" : "9", "b" : "21", "guard" : 6000, "road" : "random" }, + { "a" : "9", "b" : "21", "guard" : 6000, "road" : "false" }, { "a" : "9", "b" : "23", "guard" : 6000, "road" : "false" }, { "a" : "10", "b" : "22", "guard" : 6000, "road" : "random" }, { "a" : "10", "b" : "23", "guard" : 6000, "road" : "random" }, @@ -298,7 +298,7 @@ { "a" : "13", "b" : "24", "guard" : 6000, "road" : "random" }, { "a" : "13", "b" : "14", "guard" : 6000, "road" : "true" }, { "a" : "14", "b" : "23", "guard" : 6000, "road" : "false" }, - { "a" : "14", "b" : "25", "guard" : 6000, "road" : "random" }, + { "a" : "14", "b" : "25", "guard" : 6000, "road" : "false" }, { "a" : "11", "b" : "21", "guard" : 6000, "road" : "true" }, { "a" : "11", "b" : "24", "guard" : 6000, "road" : "random" }, diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 520c2eb91..b3d9707c3 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -302,6 +302,28 @@ std::vector ZoneOptions::getConnections() const return connectionDetails; } +std::vector& ZoneOptions::getConnectionsRef() +{ + return connectionDetails; +} + +void ZoneOptions::setRoadOption(int connectionId, rmg::ERoadOption roadOption) +{ + for(auto & connection : connectionDetails) + { + if(connection.getId() == connectionId) + { + connection.setRoadOption(roadOption); + logGlobal->info("Set road option for connection %d between zones %d and %d to %s", + connectionId, connection.getZoneA(), connection.getZoneB(), + roadOption == rmg::ERoadOption::ROAD_TRUE ? "true" : + roadOption == rmg::ERoadOption::ROAD_FALSE ? "false" : "random"); + return; + } + } + logGlobal->warn("Failed to find connection with ID %d in zone %d", connectionId, id); +} + std::vector ZoneOptions::getConnectedZoneIds() const { return connectedZoneIds; @@ -481,12 +503,22 @@ rmg::ERoadOption ZoneConnection::getRoadOption() const { return hasRoad; } + +void ZoneConnection::setRoadOption(rmg::ERoadOption roadOption) +{ + hasRoad = roadOption; +} bool operator==(const ZoneConnection & l, const ZoneConnection & r) { return l.id == r.id; } +bool operator<(const ZoneConnection & l, const ZoneConnection & r) +{ + return l.id < r.id; +} + void ZoneConnection::serializeJson(JsonSerializeFormat & handler) { static const std::vector connectionTypes = diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 649f4878d..7a0601a94 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -105,10 +105,12 @@ public: int getGuardStrength() const; rmg::EConnectionType getConnectionType() const; rmg::ERoadOption getRoadOption() const; + void setRoadOption(rmg::ERoadOption roadOption); void serializeJson(JsonSerializeFormat & handler); friend bool operator==(const ZoneConnection &, const ZoneConnection &); + friend bool operator<(const ZoneConnection &, const ZoneConnection &); private: int id; TRmgTemplateZoneId zoneA; @@ -185,8 +187,12 @@ public: void addConnection(const ZoneConnection & connection); std::vector getConnections() const; + std::vector& getConnectionsRef(); std::vector getConnectedZoneIds() const; + // Set road option for a specific connection by ID + void setRoadOption(int connectionId, rmg::ERoadOption roadOption); + void serializeJson(JsonSerializeFormat & handler); EMonsterStrength::EMonsterStrength monsterStrength; diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 2714c9523..5016cd1bc 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -1008,127 +1008,104 @@ void CZonePlacer::assignZones(vstd::RNG * rand) void CZonePlacer::dropRandomRoads(vstd::RNG * rand) { auto zones = map.getZones(); + bool anyDropped; - //First, build a graph of road connections - std::map> roadGraph; - std::vector randomConnections; - std::vector fixedConnections; - - //Collect all road connections and build initial graph - for(const auto & zone : zones) + do { - for(const auto & connection : zone.second->getConnections()) + anyDropped = false; + std::map> roadGraph; + std::set randomConnections; + + //Build graph and collect random connections + for(const auto & zone : zones) { - if(connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE) + for(const auto & connection : zone.second->getConnections()) { - roadGraph[connection.getZoneA()].insert(connection.getZoneB()); - roadGraph[connection.getZoneB()].insert(connection.getZoneA()); - fixedConnections.push_back(connection); - } - else if(connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM) - { - roadGraph[connection.getZoneA()].insert(connection.getZoneB()); - roadGraph[connection.getZoneB()].insert(connection.getZoneA()); - randomConnections.push_back(connection); - } - } - } - - //Find all connected components in the initial graph - std::map zoneToComponent; - int numComponents = 0; - - auto dfsComponent = [&](TRmgTemplateZoneId start, int component) - { - std::stack stack; - stack.push(start); - - while(!stack.empty()) - { - auto current = stack.top(); - stack.pop(); - - if(zoneToComponent.find(current) != zoneToComponent.end()) - continue; - - zoneToComponent[current] = component; - - for(auto neighbor : roadGraph[current]) - { - if(zoneToComponent.find(neighbor) == zoneToComponent.end()) + if(connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE) { - stack.push(neighbor); + roadGraph[connection.getZoneA()].insert(connection.getZoneB()); + roadGraph[connection.getZoneB()].insert(connection.getZoneA()); } + else if(connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM) + { + roadGraph[connection.getZoneA()].insert(connection.getZoneB()); + roadGraph[connection.getZoneB()].insert(connection.getZoneA()); + randomConnections.insert(connection); + } + // ROAD_FALSE connections are ignored } } - }; + logGlobal->info("Remaining random connections: %d", randomConnections.size()); - //Find all components - for(const auto & zone : zones) - { - if(zoneToComponent.find(zone.first) == zoneToComponent.end() && !roadGraph[zone.first].empty()) - { - dfsComponent(zone.first, numComponents++); - } - } + if(randomConnections.empty()) + break; - //Process each component separately - for(int component = 0; component < numComponents; component++) - { - //Get random connections for this component - std::vector componentRandomConnections; - for(const auto & conn : randomConnections) + //Convert to vector for shuffling + std::vector shuffledConnections(randomConnections.begin(), randomConnections.end()); + RandomGeneratorUtil::randomShuffle(shuffledConnections, *rand); + + //Try each random connection in shuffled order + for(const auto & conn : shuffledConnections) { - if(zoneToComponent[conn.getZoneA()] == component) + auto zoneA = conn.getZoneA(); + auto zoneB = conn.getZoneB(); + + //Check if either zone would become isolated by removing this connection + if(roadGraph[zoneA].size() <= 1 || roadGraph[zoneB].size() <= 1) { - componentRandomConnections.push_back(conn); + //Can't remove this connection as it would isolate a zone + continue; } - } - //Shuffle random connections - RandomGeneratorUtil::randomShuffle(componentRandomConnections, *rand); - - //Try removing each random connection - for(const auto & conn : componentRandomConnections) - { //Temporarily remove this connection - roadGraph[conn.getZoneA()].erase(conn.getZoneB()); - roadGraph[conn.getZoneB()].erase(conn.getZoneA()); + roadGraph[zoneA].erase(zoneB); + roadGraph[zoneB].erase(zoneA); - //Check if graph remains connected + //Check if graph remains connected as a whole bool canRemove = true; std::set visited; - //Start DFS from any zone in this component - auto startZone = conn.getZoneA(); - std::stack stack; - stack.push(startZone); - - while(!stack.empty()) + // Get all zones that have road connections + std::set zonesWithRoads; + for(const auto & entry : roadGraph) { - auto current = stack.top(); - stack.pop(); - - if(visited.find(current) != visited.end()) - continue; - - visited.insert(current); - - for(auto neighbor : roadGraph[current]) + if(!entry.second.empty()) { - if(visited.find(neighbor) == visited.end()) - { - stack.push(neighbor); - } + zonesWithRoads.insert(entry.first); } } - //Check if all zones in this component are still reachable - for(const auto & zone : zones) + if(!zonesWithRoads.empty()) { - if(zoneToComponent[zone.first] == component && !roadGraph[zone.first].empty()) + //Start DFS from any zone that has roads + TRmgTemplateZoneId startZone = *zonesWithRoads.begin(); + + std::stack stack; + stack.push(startZone); + + while(!stack.empty()) { - if(visited.find(zone.first) == visited.end()) + auto current = stack.top(); + stack.pop(); + + if(visited.find(current) != visited.end()) + continue; + + visited.insert(current); + + for(auto neighbor : roadGraph[current]) + { + if(visited.find(neighbor) == visited.end()) + { + stack.push(neighbor); + } + } + } + + //Check if all zones with roads are still reachable + for(auto zoneId : zonesWithRoads) + { + if(visited.find(zoneId) == visited.end()) { canRemove = false; break; @@ -1138,24 +1115,44 @@ void CZonePlacer::dropRandomRoads(vstd::RNG * rand) if(!canRemove) { - //Restore connection if removing it would break connectivity - roadGraph[conn.getZoneA()].insert(conn.getZoneB()); - roadGraph[conn.getZoneB()].insert(conn.getZoneA()); + //Restore connection and try next one + roadGraph[zoneA].insert(zoneB); + roadGraph[zoneB].insert(zoneA); + continue; } - else + + //Found a connection we can remove - update in both zones that contain it + auto & zonePtr = zones[zoneA]; + zonePtr->setRoadOption(conn.getId(), rmg::ERoadOption::ROAD_FALSE); + + auto & otherZonePtr = zones[zoneB]; + otherZonePtr->setRoadOption(conn.getId(), rmg::ERoadOption::ROAD_FALSE); + + logGlobal->info("Dropped random road between %d and %d", zoneA, zoneB); + anyDropped = true; + break; //Exit loop and rebuild graph + } + } while(anyDropped); + + // Use a set to track processed connection IDs to avoid duplicates + std::set processedConnectionIds; + + // Process each zone's connections + for(auto & zonePtr : zones) + { + for(auto & connection : zonePtr.second->getConnections()) + { + if(connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM) { - //Mark this connection as having no road - for(auto & zonePtr : zones) - { - for(auto & connection : zonePtr.second->getConnections()) - { - if(connection.getId() == conn.getId()) - { - const_cast(connection) = conn; //FIXME: avoid const_cast - break; - } - } - } + auto id = connection.getId(); + // Only process each connection once + if(vstd::contains(processedConnectionIds, id)) + continue; + + processedConnectionIds.insert(id); + + // Use the new setRoadOption method + zonePtr.second->setRoadOption(id, rmg::ERoadOption::ROAD_TRUE); } } } diff --git a/lib/rmg/modificators/ConnectionsPlacer.cpp b/lib/rmg/modificators/ConnectionsPlacer.cpp index 612e3872a..2055ab653 100644 --- a/lib/rmg/modificators/ConnectionsPlacer.cpp +++ b/lib/rmg/modificators/ConnectionsPlacer.cpp @@ -110,14 +110,10 @@ void ConnectionsPlacer::init() POSTFUNCTION(RoadPlacer); POSTFUNCTION(ObjectManager); - auto id = zone.getId(); - for(auto c : map.getMapGenOptions().getMapTemplate()->getConnectedZoneIds()) + // FIXME: Use zones modified by CZonePlacer + for (auto c : zone.getConnections()) { - // Only consider connected zones - if (c.getZoneA() == id || c.getZoneB() == id) - { - addConnection(c); - } + addConnection(c); } } @@ -477,6 +473,10 @@ void ConnectionsPlacer::collectNeighbourZones() bool ConnectionsPlacer::shouldGenerateRoad(const rmg::ZoneConnection& connection) const { + if (connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM) + logGlobal->error("Random road between zones %d and %d", connection.getZoneA(), connection.getZoneB()); + else + logGlobal->info("Should generate road between zones %d and %d: %d", connection.getZoneA(), connection.getZoneB(), connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE); return connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE; }