Browse Source

Add townHints to random template, define logic

Tomasz Zieliński 7 months ago
parent
commit
946e47ee22

+ 12 - 0
config/schemas/template.json

@@ -19,6 +19,18 @@
 				"playerTowns" : {"$ref" : "#/definitions/towns"},
 				"neutralTowns" : {"$ref" : "#/definitions/towns"},
 				"matchTerrainToTown" : { "type" : "boolean"},
+				"townHints": {
+					"type": "array",
+					"items": {
+						"type": "object",
+						"properties": {
+							"likeZone": { "type": "number" },
+							"notLikeZone": { "type": "number" },
+							"relatedToZoneTerrain": { "type": "number" }
+						}
+					}
+				},
+				"townsLikeZone" : { "type" : "number" },
 				"minesLikeZone" : { "type" : "number" },
 				"terrainTypeLikeZone" : { "type" : "number" },
 				"treasureLikeZone" : { "type" : "number" },

+ 89 - 1
lib/rmg/CRmgTemplate.cpp

@@ -102,7 +102,24 @@ void ZoneOptions::CTownInfo::serializeJson(JsonSerializeFormat & handler)
 	handler.serializeInt("castles", castleCount, 0);
 	handler.serializeInt("townDensity", townDensity, 0);
 	handler.serializeInt("castleDensity", castleDensity, 0);
-	handler.serializeInt("sourceZone", sourceZone, NO_ZONE);
+	handler.serializeInt("townTypesLikeZone", townTypesLikeZone, NO_ZONE);
+	handler.serializeInt("townTypesNotLikeZone", townTypesNotLikeZone, NO_ZONE);
+	handler.serializeInt("townTypesRelatedToZoneTerrain", townTypesRelatedToZoneTerrain, NO_ZONE);
+}
+
+ZoneOptions::CTownHints::CTownHints()
+	: likeZone(NO_ZONE),
+	notLikeZone(NO_ZONE),
+	relatedToZoneTerrain(NO_ZONE)
+{
+
+}
+
+void ZoneOptions::CTownHints::serializeJson(JsonSerializeFormat & handler)
+{
+	handler.serializeInt("likeZone", likeZone, NO_ZONE);
+	handler.serializeInt("notLikeZone", notLikeZone, NO_ZONE);
+	handler.serializeInt("relatedToZoneTerrain", relatedToZoneTerrain, NO_ZONE);
 }
 
 ZoneOptions::ZoneOptions():
@@ -114,6 +131,7 @@ ZoneOptions::ZoneOptions():
 	matchTerrainToTown(true),
 	townsAreSameType(false),
 	monsterStrength(EMonsterStrength::ZONE_NORMAL),
+	townsLikeZone(NO_ZONE),
 	minesLikeZone(NO_ZONE),
 	terrainTypeLikeZone(NO_ZONE),
 	treasureLikeZone(NO_ZONE)
@@ -291,6 +309,11 @@ TRmgTemplateZoneId ZoneOptions::getCustomObjectsLikeZone() const
 	return customObjectsLikeZone;
 }
 
+TRmgTemplateZoneId ZoneOptions::getTownsLikeZone() const
+{
+	return townsLikeZone;
+}
+
 void ZoneOptions::addConnection(const ZoneConnection & connection)
 {
 	connectedZoneIds.push_back(connection.getOtherZoneId(getId()));
@@ -317,16 +340,51 @@ bool ZoneOptions::isMatchTerrainToTown() const
 	return matchTerrainToTown;
 }
 
+void ZoneOptions::setMatchTerrainToTown(bool value)
+{
+	matchTerrainToTown = value;
+}
+
 const ZoneOptions::CTownInfo & ZoneOptions::getPlayerTowns() const
 {
 	return playerTowns;
 }
 
+void ZoneOptions::setPlayerTowns(const CTownInfo & value)
+{
+	playerTowns = value;
+}
+
 const ZoneOptions::CTownInfo & ZoneOptions::getNeutralTowns() const
 {
 	return neutralTowns;
 }
 
+void ZoneOptions::setNeutralTowns(const CTownInfo & value)
+{
+	neutralTowns = value;
+}
+
+const std::vector<ZoneOptions::CTownHints> & ZoneOptions::getTownHints() const
+{
+	return townHints;
+}
+
+void ZoneOptions::setTownHints(const std::vector<CTownHints> & value)
+{
+	townHints = value;
+}
+
+std::set<FactionID> ZoneOptions::getBannedTownTypes() const
+{
+	return bannedTownTypes;
+}
+
+void ZoneOptions::setBannedTownTypes(const std::set<FactionID> & value)
+{
+	bannedTownTypes = value;
+}
+
 void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
 {
 	static const std::vector<std::string> zoneTypes =
@@ -348,6 +406,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
 
 	#define SERIALIZE_ZONE_LINK(fieldName) handler.serializeInt(#fieldName, fieldName, NO_ZONE);
 
+	SERIALIZE_ZONE_LINK(townsLikeZone);
 	SERIALIZE_ZONE_LINK(minesLikeZone);
 	SERIALIZE_ZONE_LINK(terrainTypeLikeZone);
 	SERIALIZE_ZONE_LINK(treasureLikeZone);
@@ -367,6 +426,8 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
 	handler.serializeIdArray("allowedTowns", townTypes);
 	handler.serializeIdArray("bannedTowns", bannedTownTypes);
 
+	handler.enterArray("townHints").serializeStruct(townHints);
+
 	{
 		//TODO: add support for std::map to serializeEnum
 		static const std::vector<std::string> zoneMonsterStrengths =
@@ -829,6 +890,8 @@ void CRmgTemplate::afterLoad()
 		auto zone = idAndZone.second;
 
 		// Inherit properties recursively
+		inheritTownProperties(zone);
+
 		inheritZoneProperty(zone, 
 							&rmg::ZoneOptions::getTerrainTypes, 
 							&rmg::ZoneOptions::setTerrainTypes, 
@@ -882,6 +945,31 @@ void CRmgTemplate::afterLoad()
 	allowedWaterContent.erase(EWaterContent::RANDOM);
 }
 
+void CRmgTemplate::inheritTownProperties(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration)
+{
+	if (iteration >= 50)
+	{
+		logGlobal->error("Infinite recursion for town properties detected in template %s", name);
+		return;
+	}
+
+	if (zone->getTownsLikeZone() != rmg::ZoneOptions::NO_ZONE)
+	{
+		const auto otherZone = zones.at(zone->getTownsLikeZone());
+		
+		// Recursively inherit from the source zone first
+		inheritTownProperties(otherZone, iteration + 1);
+
+		// Now copy all town-related properties from the source zone
+		zone->setPlayerTowns(otherZone->getPlayerTowns());
+		zone->setNeutralTowns(otherZone->getNeutralTowns());
+		zone->setMatchTerrainToTown(otherZone->isMatchTerrainToTown());
+		zone->setTownHints(otherZone->getTownHints());
+		zone->setTownTypes(otherZone->getTownTypes());
+		zone->setBannedTownTypes(otherZone->getBannedTownTypes());
+	}
+}
+
 // TODO: Allow any integer size which does not match enum, as well
 void CRmgTemplate::serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName)
 {

+ 32 - 7
lib/rmg/CRmgTemplate.h

@@ -142,7 +142,22 @@ public:
 		int castleDensity;
 
 		// TODO: Copy from another zone once its randomized
-		TRmgTemplateZoneId sourceZone = NO_ZONE;
+
+		TRmgTemplateZoneId townTypesLikeZone = NO_ZONE;
+		TRmgTemplateZoneId townTypesNotLikeZone = NO_ZONE;
+		TRmgTemplateZoneId townTypesRelatedToZoneTerrain = NO_ZONE;
+	};
+
+	class DLL_LINKAGE CTownHints
+	{
+	public:
+		CTownHints();
+		// TODO: Make private
+		TRmgTemplateZoneId likeZone = NO_ZONE;
+		TRmgTemplateZoneId notLikeZone = NO_ZONE;
+		TRmgTemplateZoneId relatedToZoneTerrain = NO_ZONE;
+
+		void serializeJson(JsonSerializeFormat & handler);
 	};
 
 	ZoneOptions();
@@ -162,12 +177,21 @@ public:
 	std::set<TerrainId> getDefaultTerrainTypes() const;
 
 	const CTownInfo & getPlayerTowns() const;
+	void setPlayerTowns(const CTownInfo & value);
 	const CTownInfo & getNeutralTowns() const;
-	std::set<FactionID> getDefaultTownTypes() const;
+	void setNeutralTowns(const CTownInfo & value);
+	bool isMatchTerrainToTown() const;
+	void setMatchTerrainToTown(bool value);
+	const std::vector<CTownHints> & getTownHints() const;
+	void setTownHints(const std::vector<CTownHints> & value);
 	std::set<FactionID> getTownTypes() const;
+	void setTownTypes(const std::set<FactionID> & value);
+	std::set<FactionID> getBannedTownTypes() const;
+	void setBannedTownTypes(const std::set<FactionID> & value);
+
+	std::set<FactionID> getDefaultTownTypes() const;
 	std::set<FactionID> getMonsterTypes() const;
 
-	void setTownTypes(const std::set<FactionID> & value);
 	void setMonsterTypes(const std::set<FactionID> & value);
 
 	void setMinesInfo(const std::map<TResource, ui16> & value);
@@ -192,7 +216,6 @@ public:
 	EMonsterStrength::EMonsterStrength monsterStrength;
 	
 	bool areTownsSameType() const;
-	bool isMatchTerrainToTown() const;
 
 	// Get a group of configured objects
 	const std::vector<CompoundMapObjectID> & getBannedObjects() const;
@@ -202,7 +225,8 @@ public:
 	// Copy whole custom object config from another zone
 	ObjectConfig getCustomObjects() const;
 	void setCustomObjects(const ObjectConfig & value);
-	TRmgTemplateZoneId	getCustomObjectsLikeZone() const;
+	TRmgTemplateZoneId getCustomObjectsLikeZone() const;
+	TRmgTemplateZoneId getTownsLikeZone() const;
 
 protected:
 	TRmgTemplateZoneId id;
@@ -218,6 +242,7 @@ protected:
 	std::set<TerrainId> terrainTypes;
 	std::set<TerrainId> bannedTerrains;
 	bool townsAreSameType;
+	std::vector<CTownHints> townHints; // For every town present on map
 
 	std::set<FactionID> townTypes;
 	std::set<FactionID> bannedTownTypes;
@@ -231,6 +256,7 @@ protected:
 	std::vector<TRmgTemplateZoneId> connectedZoneIds; //list of adjacent zone ids
 	std::vector<ZoneConnection> connectionDetails; //list of connections linked to that zone
 
+	TRmgTemplateZoneId townsLikeZone;
 	TRmgTemplateZoneId minesLikeZone;
 	TRmgTemplateZoneId terrainTypeLikeZone;
 	TRmgTemplateZoneId treasureLikeZone;
@@ -305,8 +331,7 @@ private:
 	std::map<TResource, ui16> inheritMineTypes(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0);
 	std::vector<CTreasureInfo> inheritTreasureInfo(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0);
 
-	// TODO: Copy custom object settings
-	// TODO: Copy town type after source town is actually randomized
+	void inheritTownProperties(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0);
 
 	void serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName);
 	void serializePlayers(JsonSerializeFormat & handler, CPlayerCountRange & value, const std::string & fieldName);

+ 50 - 7
lib/rmg/modificators/TownPlacer.cpp

@@ -46,12 +46,16 @@ void TownPlacer::process()
 
 void TownPlacer::init()
 {
+	// TODO: Depend on other zones
 	POSTFUNCTION(MinePlacer);
 	POSTFUNCTION(RoadPlacer);
 } 
 
 void TownPlacer::placeTowns(ObjectManager & manager)
 {
+	// TODO: Configurew each subseqquent town based on townHints
+	// TODO: First town should be set to type chosen by player
+
 	if(zone.getOwner() && ((zone.getType() == ETemplateZoneType::CPU_START) || (zone.getType() == ETemplateZoneType::PLAYER_START)))
 	{
 		//set zone types to player faction, generate main town
@@ -72,7 +76,7 @@ void TownPlacer::placeTowns(ObjectManager & manager)
 		else //no player - randomize town
 		{
 			player = PlayerColor::NEUTRAL;
-			zone.setTownType(getRandomTownType());
+			zone.setTownType(getTownTypeFromHint(0));
 		}
 		
 		auto townFactory = LIBRARY->objtypeh->getHandlerFor(Obj::TOWN, zone.getTownType());
@@ -114,7 +118,7 @@ void TownPlacer::placeTowns(ObjectManager & manager)
 			addNewTowns(zone.getPlayerTowns().getTownCount(), false, PlayerColor::NEUTRAL, manager);
 		}
 	}
-	else //randomize town types for any other zones as well
+	else //randomize town types for non-player zones
 	{
 		zone.setTownType(getRandomTownType());
 	}
@@ -180,20 +184,59 @@ void TownPlacer::cleanupBoundaries(const rmg::Object & rmgObject)
 	}
 }
 
+FactionID TownPlacer::getTownTypeFromHint(size_t hintIndex)
+{
+	const auto & hints = zone.getTownHints();
+	if(hints.size() <= hintIndex)
+		return zone.getTownType();
+
+	const auto & townHints = hints[hintIndex];
+	FactionID subType = zone.getTownType();
+
+	if(townHints.likeZone != rmg::ZoneOptions::NO_ZONE)
+	{
+		// Copy directly from other zone
+		subType = map.getZones().at(townHints.likeZone)->getTownType();
+	}
+	else if(townHints.notLikeZone != rmg::ZoneOptions::NO_ZONE)
+	{
+		// Exclude type rolled for other zone
+		auto townTypes = zone.getTownTypes();
+		townTypes.erase(map.getZones().at(townHints.notLikeZone)->getTownType());
+		zone.setTownTypes(townTypes);
+		
+		if(!townTypes.empty())
+			subType = *RandomGeneratorUtil::nextItem(townTypes, zone.getRand());
+	}
+	else if(townHints.relatedToZoneTerrain != rmg::ZoneOptions::NO_ZONE)
+	{
+		auto townTerrain = map.getZones().at(townHints.relatedToZoneTerrain)->getTerrainType();
+		
+		auto townTypesAllowed = zone.getTownTypes();
+		vstd::erase_if(townTypesAllowed, [townTerrain](FactionID type)
+		{
+			return (*LIBRARY->townh)[type]->getNativeTerrain() != townTerrain;
+		});
+		zone.setTownTypes(townTypesAllowed);
+		
+		if(!townTypesAllowed.empty())
+			subType = *RandomGeneratorUtil::nextItem(townTypesAllowed, zone.getRand());
+	}
+
+	return subType;
+}
+
 void TownPlacer::addNewTowns(int count, bool hasFort, const PlayerColor & player, ObjectManager & manager)
 {
 	for(int i = 0; i < count; i++)
 	{
 		FactionID subType = zone.getTownType();
 		
-		if(totalTowns>0)
+		if(totalTowns > 0)
 		{
 			if(!zone.areTownsSameType())
 			{
-				if(!zone.getTownTypes().empty())
-					subType = *RandomGeneratorUtil::nextItem(zone.getTownTypes(), zone.getRand());
-				else
-					subType = *RandomGeneratorUtil::nextItem(zone.getDefaultTownTypes(), zone.getRand()); //it is possible to have zone with no towns allowed
+				subType = getTownTypeFromHint(totalTowns);
 			}
 		}
 		

+ 1 - 0
lib/rmg/modificators/TownPlacer.h

@@ -29,6 +29,7 @@ protected:
 	void cleanupBoundaries(const rmg::Object & rmgObject);
 	void addNewTowns(int count, bool hasFort, const PlayerColor & player, ObjectManager & manager);
 	FactionID getRandomTownType(bool matchUndergroundType = false);
+	FactionID getTownTypeFromHint(size_t hintIndex);
 	void placeTowns(ObjectManager & manager);
 	bool placeMines(ObjectManager & manager);
 	int3 placeMainTown(ObjectManager & manager, CGTownInstance & town);