1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

+ Handle Wide Connections

+ Possibly hide fictive and repulsive connections, needs testing
This commit is contained in:
Tomasz Zieliński 2023-06-17 19:09:38 +02:00
parent 02ea798c97
commit 65d10cf9f2
5 changed files with 180 additions and 27 deletions

View File

@ -12,6 +12,7 @@
#include <vstd/ContainerUtils.h>
#include <boost/bimap.hpp>
#include "CRmgTemplate.h"
#include "Functions.h"
#include "../VCMI_Lib.h"
#include "../CTownHandler.h"
@ -285,14 +286,20 @@ TRmgTemplateZoneId ZoneOptions::getTreasureLikeZone() const
return treasureLikeZone;
}
void ZoneOptions::addConnection(TRmgTemplateZoneId otherZone)
void ZoneOptions::addConnection(const ZoneConnection & connection)
{
connections.push_back (otherZone);
connectedZoneIds.push_back(connection.getOtherZoneId(getId()));
connectionDetails.push_back(connection);
}
std::vector<TRmgTemplateZoneId> ZoneOptions::getConnections() const
std::vector<ZoneConnection> ZoneOptions::getConnections() const
{
return connections;
return connectionDetails;
}
std::vector<TRmgTemplateZoneId> ZoneOptions::getConnectedZoneIds() const
{
return connectedZoneIds;
}
bool ZoneOptions::areTownsSameType() const
@ -429,7 +436,8 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
ZoneConnection::ZoneConnection()
: zoneA(-1),
zoneB(-1),
guardStrength(0)
guardStrength(0),
connectionType(EConnectionType::EConnectionType::GUARDED)
{
}
@ -444,10 +452,31 @@ TRmgTemplateZoneId ZoneConnection::getZoneB() const
return zoneB;
}
TRmgTemplateZoneId ZoneConnection::getOtherZoneId(TRmgTemplateZoneId id) const
{
if (id == zoneA)
{
return zoneB;
}
else if (id == zoneB)
{
return zoneA;
}
else
{
throw rmgException("Zone does not belong to this connection");
}
}
int ZoneConnection::getGuardStrength() const
{
return guardStrength;
}
EConnectionType::EConnectionType ZoneConnection::getConnectionType() const
{
return connectionType;
}
bool operator==(const ZoneConnection & l, const ZoneConnection & r)
{
@ -456,9 +485,18 @@ bool operator==(const ZoneConnection & l, const ZoneConnection & r)
void ZoneConnection::serializeJson(JsonSerializeFormat & handler)
{
static const std::vector<std::string> connectionTypes =
{
"guarded",
"fictive",
"repulsive",
"wide"
};
handler.serializeId<TRmgTemplateZoneId, TRmgTemplateZoneId, ZoneEncoder>("a", zoneA, -1);
handler.serializeId<TRmgTemplateZoneId, TRmgTemplateZoneId, ZoneEncoder>("b", zoneB, -1);
handler.serializeInt("guard", guardStrength, 0);
handler.serializeEnum("type", connectionType, connectionTypes);
}
}
@ -526,9 +564,9 @@ const CRmgTemplate::Zones & CRmgTemplate::getZones() const
return zones;
}
const std::vector<ZoneConnection> & CRmgTemplate::getConnections() const
const std::vector<ZoneConnection> & CRmgTemplate::getConnectedZoneIds() const
{
return connections;
return connectedZoneIds;
}
void CRmgTemplate::validate() const
@ -644,7 +682,7 @@ void CRmgTemplate::serializeJson(JsonSerializeFormat & handler)
{
auto connectionsData = handler.enterArray("connections");
connectionsData.serializeStruct(connections);
connectionsData.serializeStruct(connectedZoneIds);
}
{
@ -759,16 +797,18 @@ void CRmgTemplate::afterLoad()
inheritTreasureInfo(zone);
}
for(const auto & connection : connections)
for(const auto & connection : connectedZoneIds)
{
//TODO: Remember connection details and allow to access them from anywhere
auto id1 = connection.getZoneA();
auto id2 = connection.getZoneB();
auto zone1 = zones.at(id1);
auto zone2 = zones.at(id2);
zone1->addConnection(id2);
zone2->addConnection(id1);
zone1->addConnection(connection);
zone2->addConnection(connection);
}
if(allowedWaterContent.empty() || allowedWaterContent.count(EWaterContent::RANDOM))

View File

@ -67,17 +67,31 @@ public:
void serializeJson(JsonSerializeFormat & handler);
};
namespace EConnectionType
{
enum class EConnectionType
{
GUARDED = 0, //default
FICTIVE,
REPULSIVE,
WIDE
};
}
namespace rmg
{
class DLL_LINKAGE ZoneConnection
{
public:
ZoneConnection();
TRmgTemplateZoneId getZoneA() const;
TRmgTemplateZoneId getZoneB() const;
TRmgTemplateZoneId getOtherZoneId(TRmgTemplateZoneId id) const;
int getGuardStrength() const;
EConnectionType::EConnectionType getConnectionType() const;
void serializeJson(JsonSerializeFormat & handler);
@ -86,6 +100,7 @@ private:
TRmgTemplateZoneId zoneA;
TRmgTemplateZoneId zoneB;
int guardStrength;
EConnectionType::EConnectionType connectionType;
};
class DLL_LINKAGE ZoneOptions
@ -149,8 +164,9 @@ public:
TRmgTemplateZoneId getTerrainTypeLikeZone() const;
TRmgTemplateZoneId getTreasureLikeZone() const;
void addConnection(TRmgTemplateZoneId otherZone);
std::vector<TRmgTemplateZoneId> getConnections() const;
void addConnection(const ZoneConnection & connection);
std::vector<ZoneConnection> getConnections() const;
std::vector<TRmgTemplateZoneId> getConnectedZoneIds() const;
void serializeJson(JsonSerializeFormat & handler);
@ -178,7 +194,8 @@ protected:
std::vector<CTreasureInfo> treasureInfo;
std::vector<TRmgTemplateZoneId> connections; //list of adjacent zones
std::vector<TRmgTemplateZoneId> connectedZoneIds; //list of adjacent zone ids
std::vector<ZoneConnection> connectionDetails; //list of connections linked to that zone
TRmgTemplateZoneId minesLikeZone;
TRmgTemplateZoneId terrainTypeLikeZone;
@ -223,7 +240,7 @@ public:
const CPlayerCountRange & getCpuPlayers() const;
std::pair<int3, int3> getMapSizes() const;
const Zones & getZones() const;
const std::vector<rmg::ZoneConnection> & getConnections() const;
const std::vector<rmg::ZoneConnection> & getConnectedZoneIds() const;
void validate() const; /// Tests template on validity and throws exception on failure
@ -235,7 +252,7 @@ private:
int3 minSize, maxSize;
CPlayerCountRange players, cpuPlayers;
Zones zones;
std::vector<rmg::ZoneConnection> connections;
std::vector<rmg::ZoneConnection> connectedZoneIds;
std::set<EWaterContent::EWaterContent> allowedWaterContent;
void afterLoad();

View File

@ -71,10 +71,16 @@ void CZonePlacer::findPathsBetweenZones()
q.pop();
const auto& currentZone = zones.at(current);
const auto& connections = currentZone->getConnections();
const auto& connectedZoneIds = currentZone->getConnections();
for (uint32_t neighbor : connections)
for (auto & connection : connectedZoneIds)
{
//TODO: Access information about connection type
if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
{
continue;
}
auto neighbor = connection.getOtherZoneId(current);
if (!visited[neighbor])
{
visited[neighbor] = true;
@ -132,7 +138,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand)
{
case ETemplateZoneType::PLAYER_START:
case ETemplateZoneType::CPU_START:
if (firstZone->getConnections().size() > 2)
if (firstZone->getConnectedZoneIds().size() > 2)
{
getRandomEdge(x, y);
}
@ -180,7 +186,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand)
for (size_t i = 1; i < zones.size(); i++)
{
auto zone = zonesVector[i].second;
auto connections = zone->getConnections();
auto connectedZoneIds = zone->getConnectedZoneIds();
float maxDistance = -1000.0;
int3 mostDistantPlace;
@ -521,9 +527,14 @@ void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces,
float3 pos = zone.second->getCenter();
float totalDistance = 0;
for (auto con : zone.second->getConnections())
for (const auto & connection : zone.second->getConnections())
{
auto otherZone = zones[con];
if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
{
continue;
}
auto otherZone = zones[connection.getOtherZoneId(zone.second->getId())];
float3 otherZoneCenter = otherZone->getCenter();
auto distance = static_cast<float>(pos.dist2d(otherZoneCenter));
@ -601,6 +612,24 @@ void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces
{
pushAwayFromBoundary(pos.x, 1);
}
//Always move repulsive zones away, no matter their distance
//TODO: Consider z plane?
for (auto& connection : zone.second->getConnections())
{
if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
{
auto & otherZone = zones[connection.getOtherZoneId(zone.second->getId())];
float3 otherZoneCenter = otherZone->getCenter();
//TODO: Roll into lambda?
auto distance = static_cast<float>(pos.dist2d(otherZoneCenter));
float minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize;
float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness;
forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone->getId()]);
}
}
overlaps[zone.second] = overlap;
forceVector.z = 0; //operator - doesn't preserve z coordinate :/
forces[zone.second] = forceVector;
@ -656,7 +685,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
//Only swap zones on the same level
//Don't swap zones that should be connected (Jebus)
if (misplacedZones[i].second->getCenter().z == level &&
!vstd::contains(firstZone->getConnections(), misplacedZones[i].second->getId()))
!vstd::contains(firstZone->getConnectedZoneIds(), misplacedZones[i].second->getId()))
{
secondZone = misplacedZones[i].second;
break;
@ -688,7 +717,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
//Move one zone towards most distant zone to reduce distance
float maxDistance = 0;
for (auto con : misplacedZone->getConnections())
for (auto con : misplacedZone->getConnectedZoneIds())
{
auto otherZone = zones[con];
float distance = static_cast<float>(otherZone->getCenter().dist2dSQ(ourCenter));

View File

@ -78,7 +78,7 @@ void ConnectionsPlacer::init()
POSTFUNCTION(RoadPlacer);
POSTFUNCTION(ObjectManager);
for(auto c : map.getMapGenOptions().getMapTemplate()->getConnections())
for(auto c : map.getMapGenOptions().getMapTemplate()->getConnectedZoneIds())
addConnection(c);
}
@ -107,8 +107,58 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
|| vstd::contains(otherTerrain->prohibitTransitions, zone.getTerrainType());
auto directConnectionIterator = dNeighbourZones.find(otherZoneId);
if (directConnectionIterator != dNeighbourZones.end())
{
if (connection.getConnectionType() == EConnectionType::EConnectionType::WIDE)
{
for (auto borderPos : directConnectionIterator->second)
{
//TODO: Refactor common code with direct connection
int3 potentialPos = zone.areaPossible().nearest(borderPos);
assert(borderPos != potentialPos);
auto safetyGap = rmg::Area({ potentialPos });
safetyGap.unite(safetyGap.getBorderOutside());
safetyGap.intersect(zone.areaPossible());
if (!safetyGap.empty())
{
safetyGap.intersect(otherZone->areaPossible());
if (safetyGap.empty())
{
rmg::Area border(zone.getArea().getBorder());
border.unite(otherZone->getArea().getBorder());
auto costFunction = [&border](const int3& s, const int3& d)
{
return 1.f / (1.f + border.distanceSqr(d));
};
auto ourArea = zone.areaPossible() + zone.freePaths();
auto theirArea = otherZone->areaPossible() + otherZone->freePaths();
theirArea.add(potentialPos);
rmg::Path ourPath(ourArea);
rmg::Path theirPath(theirArea);
ourPath.connect(zone.freePaths());
ourPath = ourPath.search(potentialPos, true, costFunction);
theirPath.connect(otherZone->freePaths());
theirPath = theirPath.search(potentialPos, true, costFunction);
if (ourPath.valid() && theirPath.valid())
{
zone.connectPath(ourPath);
otherZone->connectPath(theirPath);
success = true;
break;
}
}
}
}
}
}
float maxDist = -10e6;
if(!directProhibited && directConnectionIterator != dNeighbourZones.end())
if(!success && !directProhibited && directConnectionIterator != dNeighbourZones.end())
{
int3 guardPos(-1, -1, -1);
int3 roadNode;
@ -333,6 +383,23 @@ void ConnectionsPlacer::createBorder()
return map.isOnMap(tile) && map.getZones()[map.getZoneID(tile)]->getType() != ETemplateZoneType::WATER;
});
//No border for wide connections
for (auto& connection : zone.getConnections()) // We actually placed that connection already
{
auto otherZone = connection.getOtherZoneId(zone.getId());
if (connection.getConnectionType() == EConnectionType::EConnectionType::WIDE)
{
auto sharedBorder = borderArea.getSubarea([this, otherZone, &connection, &borderOutsideArea](const int3 & t)
{
auto tile = borderOutsideArea.nearest(t);
return map.isOnMap(tile) && map.getZones()[map.getZoneID(tile)]->getId() == otherZone;
});
blockBorder.subtract(sharedBorder);
}
};
Zone::Lock lock(zone.areaMutex); //Protect from erasing same tiles again
for(const auto & tile : blockBorder.getTilesVector())
{

View File

@ -404,7 +404,7 @@ void TreasurePlacer::addAllPossibleObjects()
//Seer huts with creatures or generic rewards
if(zone.getConnections().size()) //Unlikely, but...
if(zone.getConnectedZoneIds().size()) //Unlikely, but...
{
auto * qap = zone.getModificator<QuestArtifactPlacer>();
if(!qap)