Browse Source

Allow filtering obstacles by faction aligmnment

Tomasz Zieliński 1 year ago
parent
commit
a356fdaf2a

+ 14 - 0
config/schemas/biome.json

@@ -27,6 +27,20 @@
 						}
 					]
 					
+				},
+				"alignment" : {
+					"anyOf": [
+						{
+							"type" : "string",
+							"enum" : ["good", "evil", "neutral"],
+							"description" : "Alignment of faction of the zone"
+						},
+						{
+							"type" : "array",
+							"items" : { "type" : "string" },
+							"description" : "Alignment of faction of the zone"
+						}
+					]
 				}
 			}
 		},

+ 2 - 1
lib/constants/Enumerations.h

@@ -13,7 +13,8 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 enum class EAlignment : int8_t
 {
-	GOOD,
+	ANY = -1,
+	GOOD = 0,
 	EVIL,
 	NEUTRAL
 };

+ 69 - 5
lib/mapObjects/ObstacleSetHandler.cpp

@@ -12,6 +12,7 @@
 #include "ObstacleSetHandler.h"
 
 #include "../modding/IdentifierStorage.h"
+#include "../constants/stringConstants.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -32,21 +33,41 @@ void ObstacleSet::addObstacle(std::shared_ptr<const ObjectTemplate> obstacle)
 	obstacles.push_back(obstacle);
 }
 
-ObstacleSetFilter::ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain = TerrainId::ANY_TERRAIN):
+ObstacleSetFilter::ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain = TerrainId::ANY_TERRAIN, EAlignment alignment = EAlignment::ANY):
 	allowedTypes(allowedTypes),
-	terrain(terrain)
+	terrain(terrain),
+	alignment(alignment)
 {
 }
 
-ObstacleSetFilter::ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain = TerrainId::ANY_TERRAIN):
+ObstacleSetFilter::ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain = TerrainId::ANY_TERRAIN, EAlignment alignment = EAlignment::ANY):
 	allowedTypes({allowedType}),
-	terrain(terrain)
+	terrain(terrain),
+	alignment(alignment)
 {
 }
 
 bool ObstacleSetFilter::filter(const ObstacleSet &set) const
 {
-	return (vstd::contains(set.getTerrains(), terrain) || terrain == TerrainId::ANY_TERRAIN);
+	if (terrain != TerrainId::ANY_TERRAIN && !vstd::contains(set.getTerrains(), terrain))
+	{
+		return false;
+	}
+
+	// TODO: Also check specific factions
+	auto alignments = set.getAlignments();
+
+	if (alignment != EAlignment::ANY && !alignments.empty() && !vstd::contains(alignments, alignment))
+	{
+		return false;
+	}
+
+	return true;
+}
+
+TerrainId ObstacleSetFilter::getTerrain() const
+{
+	return terrain;
 }
 
 std::set<TerrainId> ObstacleSet::getTerrains() const
@@ -69,6 +90,16 @@ void ObstacleSet::addTerrain(TerrainId terrain)
 	this->allowedTerrains.insert(terrain);
 }
 
+void ObstacleSet::addAlignment(EAlignment alignment)
+{
+	this->allowedAlignments.insert(alignment);
+}
+
+std::set<EAlignment> ObstacleSet::getAlignments() const
+{
+	return allowedAlignments;
+}
+
 ObstacleSet::EObstacleType ObstacleSet::getType() const
 {
 	return type;
@@ -185,6 +216,16 @@ std::vector<ObstacleSet::EObstacleType> ObstacleSetFilter::getAllowedTypes() con
 	return allowedTypes;
 }
 
+void ObstacleSetFilter::setType(ObstacleSet::EObstacleType type)
+{
+	allowedTypes = {type};
+}
+
+void ObstacleSetFilter::setTypes(std::vector<ObstacleSet::EObstacleType> types)
+{
+	this->allowedTypes = types;
+}
+
 std::vector<JsonNode> ObstacleSetHandler::loadLegacyData()
 {
 	return {};
@@ -248,6 +289,29 @@ std::shared_ptr<ObstacleSet> ObstacleSetHandler::loadFromJson(const std::string
 		}
 	}
 
+	// TODO: Move this parser to some utils
+	auto parseAlignment = [](const std::string & str) ->EAlignment
+	{
+		int alignment = vstd::find_pos(GameConstants::ALIGNMENT_NAMES, str);
+		if (alignment == -1)
+			logGlobal->error("Incorrect alignment: ", str);
+		else
+			return static_cast<EAlignment>(alignment);
+	};
+
+	if (json["alignment"].isString())
+	{
+		os->addAlignment(parseAlignment(json["alignment"].String()));
+	}
+	else if (json["alignment"].isVector())
+	{
+		auto alignments = json["alignment"].Vector();
+		for (const auto & node : alignments)
+		{
+			os->addAlignment(parseAlignment(node.String()));
+		}
+	}
+
 	auto templates = json["templates"].Vector();
 	for (const auto & node : templates)
 	{

+ 11 - 3
lib/mapObjects/ObstacleSetHandler.h

@@ -50,6 +50,8 @@ public:
 	void setTerrain(TerrainId terrain);
 	void setTerrains(const std::set<TerrainId> & terrains);
 	void addTerrain(TerrainId terrain);
+	std::set<EAlignment> getAlignments() const;
+	void addAlignment(EAlignment alignment);
 
 	static EObstacleType typeFromString(const std::string &str);
 	std::string toString() const;
@@ -59,6 +61,7 @@ public:
 private:
 	EObstacleType type;
 	std::set<TerrainId> allowedTerrains;
+	std::set<EAlignment> allowedAlignments; // Empty means all
 	std::vector<std::shared_ptr<const ObjectTemplate>> obstacles;
 };
 
@@ -67,17 +70,22 @@ typedef std::vector<std::shared_ptr<ObstacleSet>> TObstacleTypes;
 class DLL_LINKAGE ObstacleSetFilter
 {
 public:
-	ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain);
-	ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain);
+	ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain , EAlignment alignment);
+	ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain, EAlignment alignment);
 
 	bool filter(const ObstacleSet &set) const;
 
+	void setType(ObstacleSet::EObstacleType type);
+	void setTypes(std::vector<ObstacleSet::EObstacleType> types);
 	std::vector<ObstacleSet::EObstacleType> getAllowedTypes() const;
 	TerrainId getTerrain() const;
 
+	void setAlignment(EAlignment alignment);
+
 private:
 	std::vector<ObstacleSet::EObstacleType> allowedTypes;
-// TODO: Filter by faction, alignment, surface/underground, etc.
+	EAlignment alignment;
+// TODO: Filter by faction,  surface/underground, etc.
 	const TerrainId terrain;
 };
 

+ 19 - 12
lib/mapping/ObstacleProxy.cpp

@@ -52,7 +52,7 @@ void ObstacleProxy::sortObstacles()
 	});
 }
 
-bool ObstacleProxy::prepareBiome(TerrainId terrain, CRandomGenerator & rand)
+bool ObstacleProxy::prepareBiome(const ObstacleSetFilter & filter, CRandomGenerator & rand)
 {
 	// FIXME: All the mountains have same ID and mostly same subID, how to differentiate them?
 
@@ -68,7 +68,11 @@ bool ObstacleProxy::prepareBiome(TerrainId terrain, CRandomGenerator & rand)
 	const size_t MIN_SMALL_SETS = 3;
 	const size_t MAX_SMALL_SETS = 5;
 
-	TObstacleTypes mountainSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter(ObstacleSet::EObstacleType::MOUNTAINS, terrain));
+	auto terrain = filter.getTerrain();
+	auto localFilter = filter;
+	localFilter.setType(ObstacleSet::EObstacleType::MOUNTAINS);
+
+	TObstacleTypes mountainSets = VLC->biomeHandler->getObstacles(localFilter);
 
 	if (!mountainSets.empty())
 	{
@@ -78,11 +82,12 @@ bool ObstacleProxy::prepareBiome(TerrainId terrain, CRandomGenerator & rand)
 	}
 	else
 	{
-		logGlobal->warn("No mountain sets found for terrain %s", terrain.encode(terrain.getNum()));
+		logGlobal->warn("No mountain sets found for terrain %s", TerrainId::encode(terrain.getNum()));
 		// FIXME: Do we ever want to generate obstacles without any mountains?
 	}
 
-	TObstacleTypes treeSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter(ObstacleSet::EObstacleType::TREES, terrain));
+	localFilter.setType(ObstacleSet::EObstacleType::TREES);
+	TObstacleTypes treeSets = VLC->biomeHandler->getObstacles(localFilter);
 
 	// 1 or 2 tree sets
 	size_t treeSetsCount = std::min<size_t>(treeSets.size(), rand.nextInt(1, 2));
@@ -94,8 +99,8 @@ bool ObstacleProxy::prepareBiome(TerrainId terrain, CRandomGenerator & rand)
 	logGlobal->info("Added %d tree sets", treeSetsCount);
 
 	// Some obstacle types may be completely missing from water, but it's not a problem
-	TObstacleTypes largeSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter({ObstacleSet::EObstacleType::LAKES, ObstacleSet::EObstacleType::CRATERS},
-	terrain));
+	localFilter.setTypes({ObstacleSet::EObstacleType::LAKES, ObstacleSet::EObstacleType::CRATERS});
+	TObstacleTypes largeSets = VLC->biomeHandler->getObstacles(localFilter);
 
 	// We probably don't want to have lakes and craters at the same time, choose one of them
 
@@ -108,7 +113,8 @@ bool ObstacleProxy::prepareBiome(TerrainId terrain, CRandomGenerator & rand)
 		logGlobal->info("Added large set of type %s", obstacleSets.back()->getType());
 	}
 
-	TObstacleTypes rockSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter(ObstacleSet::EObstacleType::ROCKS, terrain));
+	localFilter.setType(ObstacleSet::EObstacleType::ROCKS);
+	TObstacleTypes rockSets = VLC->biomeHandler->getObstacles(localFilter);
 
 	size_t rockSetsCount = std::min<size_t>(rockSets.size(), rand.nextInt(1, 2));
 	for (size_t i = 0; i < rockSetsCount; i++)
@@ -118,7 +124,8 @@ bool ObstacleProxy::prepareBiome(TerrainId terrain, CRandomGenerator & rand)
 	}
 	logGlobal->info("Added %d rock sets", rockSetsCount);
 
-	TObstacleTypes plantSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter(ObstacleSet::EObstacleType::PLANTS, terrain));
+	localFilter.setType(ObstacleSet::EObstacleType::PLANTS);
+	TObstacleTypes plantSets = VLC->biomeHandler->getObstacles(localFilter);
 
 	// 1 or 2 sets (3 - rock sets)
 	size_t plantSetsCount = std::min<size_t>(plantSets.size(), rand.nextInt(1, std::max<size_t>(3 - rockSetsCount, 2)));
@@ -138,12 +145,12 @@ bool ObstacleProxy::prepareBiome(TerrainId terrain, CRandomGenerator & rand)
 
 	size_t smallSets = rand.nextInt(MIN_SMALL_SETS, maxSmallSets);
 
-	TObstacleTypes smallObstacleSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter({ObstacleSet::EObstacleType::STRUCTURES, ObstacleSet::EObstacleType::ANIMALS},
-	terrain));
+	localFilter.setTypes({ObstacleSet::EObstacleType::STRUCTURES, ObstacleSet::EObstacleType::ANIMALS});
+	TObstacleTypes smallObstacleSets = VLC->biomeHandler->getObstacles(localFilter);
 	RandomGeneratorUtil::randomShuffle(smallObstacleSets, rand);
 
-	TObstacleTypes otherSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter(ObstacleSet::EObstacleType::OTHER,
-	terrain));
+	localFilter.setType(ObstacleSet::EObstacleType::OTHER);
+	TObstacleTypes otherSets = VLC->biomeHandler->getObstacles(localFilter);
 	RandomGeneratorUtil::randomShuffle(otherSets, rand);
 
 	while (smallSets > 0)

+ 2 - 1
lib/mapping/ObstacleProxy.h

@@ -20,6 +20,7 @@ class CGObjectInstance;
 class ObjectTemplate;
 class CRandomGenerator;
 class IGameCallback;
+class ObstacleSetFilter;
 
 class DLL_LINKAGE ObstacleProxy
 {
@@ -29,7 +30,7 @@ public:
 	virtual ~ObstacleProxy() = default;
 
 	void collectPossibleObstacles(TerrainId terrain);
-	bool prepareBiome(TerrainId terrain, CRandomGenerator & rand);
+	bool prepareBiome(const ObstacleSetFilter & filter, CRandomGenerator & rand);
 
 	void addBlockedTile(const int3 & tile);
 

+ 5 - 1
lib/rmg/modificators/ObstaclePlacer.cpp

@@ -25,6 +25,8 @@
 #include "../../mapping/CMap.h"
 #include "../../mapping/ObstacleProxy.h"
 #include "../../mapObjects/CGObjectInstance.h"
+#include "../../mapObjects/ObstacleSetHandler.h"
+#include "../../CTownHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -34,7 +36,9 @@ void ObstaclePlacer::process()
 	if(!manager)
 		return;
 
-	if (!prepareBiome(zone.getTerrainType(), zone.getRand()))
+	ObstacleSetFilter filter(ObstacleSet::EObstacleType::INVALID, zone.getTerrainType(), zone.getTownType().toFaction()->alignment);
+
+	if (!prepareBiome(filter, zone.getRand()))
 	{
 		logGlobal->warn("Failed to prepare biome, using all possible obstacles");
 		// Use all if we fail to create proper biome