mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Merge pull request #4483 from vcmi/custom_objects_per_zone
Customizable objects in RMG zones
This commit is contained in:
		| @@ -13,6 +13,7 @@ | ||||
| #include "../../lib/VCMI_Lib.h" | ||||
| #include "../../lib/CCreatureHandler.h" | ||||
| #include "../../lib/CHeroHandler.h" | ||||
| #include "../../lib/mapObjects/CompoundMapObjectID.h" | ||||
| #include "../../lib/mapObjectConstructors/AObjectTypeHandler.h" | ||||
| #include "../../lib/mapObjects/CGHeroInstance.h" | ||||
| #include "../../lib/mapObjects/CGTownInstance.h" | ||||
|   | ||||
| @@ -22,6 +22,7 @@ | ||||
| 				"minesLikeZone" : { "type" : "number" }, | ||||
| 				"terrainTypeLikeZone" : { "type" : "number" }, | ||||
| 				"treasureLikeZone" : { "type" : "number" }, | ||||
| 				"customObjectsLikeZone" : { "type" : "number" }, | ||||
| 				 | ||||
| 				"terrainTypes": {"$ref" : "#/definitions/stringArray"}, | ||||
| 				"bannedTerrains": {"$ref" : "#/definitions/stringArray"}, | ||||
| @@ -49,6 +50,51 @@ | ||||
| 						}, | ||||
| 						"additionalProperties" : false | ||||
| 					} | ||||
| 				}, | ||||
| 				"customObjects" : { | ||||
| 					"type" : "object", | ||||
| 					"properties": { | ||||
| 						"bannedCategories": { | ||||
| 							"type": "array", | ||||
| 							"items": { | ||||
| 								"type": "string", | ||||
| 								"enum": ["all", "dwelling", "creatureBank", "randomArtifact", "bonus", "resource", "resourceGenerator", "spellScroll", "pandorasBox", "questArtifact", "seerHut"] | ||||
| 							} | ||||
| 						}, | ||||
| 						"bannedObjects": { | ||||
| 							"type": "array", | ||||
| 							"items": { | ||||
| 								"type": "string" | ||||
| 							} | ||||
| 						}, | ||||
| 						"commonObjects": { | ||||
| 							"type": "array", | ||||
| 							"items": { | ||||
| 								"type": "object", | ||||
| 								"properties": { | ||||
| 									"id": { | ||||
| 										"type": "string" | ||||
| 									}, | ||||
| 									"rmg": { | ||||
| 										"type": "object", | ||||
| 										"properties": { | ||||
| 											"value": { | ||||
| 												"type": "integer" | ||||
| 											}, | ||||
| 											"rarity": { | ||||
| 												"type": "integer" | ||||
| 											}, | ||||
| 											"zoneLimit": { | ||||
| 												"type": "integer" | ||||
| 											} | ||||
| 										}, | ||||
| 										"required": ["value", "rarity"] | ||||
| 									} | ||||
| 								}, | ||||
| 								"required": ["id", "rmg"] | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
|   | ||||
| @@ -99,10 +99,13 @@ | ||||
| 	"minesLikeZone" : 1, | ||||
| 	 | ||||
| 	// Treasures will have same configuration as in linked zone | ||||
| 	"treasureLikeZone" : 1 | ||||
| 	"treasureLikeZone" : 1, | ||||
| 	 | ||||
| 	// Terrain type will have same configuration as in linked zone | ||||
| 	"terrainTypeLikeZone" : 3 | ||||
| 	"terrainTypeLikeZone" : 3, | ||||
|  | ||||
| 	// Custom objects will have same configuration as in linked zone | ||||
| 	"customObjectsLikeZone" : 1, | ||||
|  | ||||
| 	// factions of monsters allowed on this zone | ||||
| 	"allowedMonsters" : ["inferno", "necropolis"]  | ||||
| @@ -130,6 +133,28 @@ | ||||
| 			"density" : 5 | ||||
| 		} | ||||
| 		  ... | ||||
| 	] | ||||
| 	], | ||||
|  | ||||
| 	// Objects with different configuration than default / set by mods | ||||
| 	"customObjects" : | ||||
| 	{ | ||||
| 		// All of objects of this kind will be removed from zone | ||||
| 		// Possible values: "all", "none", "creatureBank", "bonus", "dwelling", "resource", "resourceGenerator", "spellScroll", "randomArtifact", "pandorasBox", "questArtifact", "seerHut", "other | ||||
| 		"bannedCategories" : ["all", "dwelling", "creatureBank", "other"], | ||||
| 		// Specify object types and subtypes | ||||
| 		"bannedObjects" :["core:object.randomArtifactRelic"], | ||||
| 		// Configure individual common objects - overrides banned objects | ||||
| 		"commonObjects": | ||||
| 		[ | ||||
| 			{ | ||||
| 				"id" : "core:object.creatureBank.dragonFlyHive", | ||||
| 				"rmg" : { | ||||
| 					"value"		: 9000, | ||||
| 					"rarity"	: 500, | ||||
| 					"zoneLimit" : 2 | ||||
| 				} | ||||
| 			} | ||||
| 		] | ||||
| 	} | ||||
| } | ||||
| ``` | ||||
| @@ -184,6 +184,8 @@ set(lib_MAIN_SRCS | ||||
| 	rmg/TileInfo.cpp | ||||
| 	rmg/Zone.cpp | ||||
| 	rmg/Functions.cpp | ||||
| 	rmg/ObjectInfo.cpp | ||||
| 	rmg/ObjectConfig.cpp | ||||
| 	rmg/RmgMap.cpp | ||||
| 	rmg/PenroseTiling.cpp | ||||
| 	rmg/modificators/Modificator.cpp | ||||
| @@ -510,6 +512,7 @@ set(lib_MAIN_HEADERS | ||||
| 	mapObjects/IOwnableObject.h | ||||
| 	mapObjects/MapObjects.h | ||||
| 	mapObjects/MiscObjects.h | ||||
| 	mapObjects/CompoundMapObjectID.h | ||||
| 	mapObjects/ObjectTemplate.h | ||||
| 	mapObjects/ObstacleSetHandler.h | ||||
|  | ||||
| @@ -587,6 +590,8 @@ set(lib_MAIN_HEADERS | ||||
| 	rmg/RmgMap.h | ||||
| 	rmg/float3.h | ||||
| 	rmg/Functions.h | ||||
| 	rmg/ObjectInfo.h | ||||
| 	rmg/ObjectConfig.h | ||||
| 	rmg/PenroseTiling.h | ||||
| 	rmg/modificators/Modificator.h | ||||
| 	rmg/modificators/ObjectManager.h | ||||
|   | ||||
| @@ -40,6 +40,8 @@ | ||||
| #include "../texts/CGeneralTextHandler.h" | ||||
| #include "../texts/CLegacyConfigParser.h" | ||||
|  | ||||
| #include <vstd/StringUtils.h> | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| CObjectClassesHandler::CObjectClassesHandler() | ||||
| @@ -390,6 +392,62 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(CompoundMapObjectID comp | ||||
| 	return getHandlerFor(compoundIdentifier.primaryID, compoundIdentifier.secondaryID); | ||||
| } | ||||
|  | ||||
| CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::string & scope, const std::string & type, const std::string & subtype) const | ||||
| { | ||||
| 	std::optional<si32> id; | ||||
| 	if (scope.empty()) | ||||
| 	{ | ||||
| 		id = VLC->identifiers()->getIdentifier("object", type); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		id = VLC->identifiers()->getIdentifier(scope, "object", type); | ||||
| 	} | ||||
|  | ||||
| 	if(id) | ||||
| 	{ | ||||
| 		if (subtype.empty()) | ||||
| 			return CompoundMapObjectID(id.value(), 0); | ||||
|  | ||||
| 		const auto & object = mapObjectTypes.at(id.value()); | ||||
| 		std::optional<si32> subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype); | ||||
|  | ||||
| 		if (subID) | ||||
| 			return CompoundMapObjectID(id.value(), subID.value()); | ||||
| 	} | ||||
|  | ||||
| 	std::string errorString = "Failed to get id for object of type " + type + "." + subtype; | ||||
| 	logGlobal->error(errorString); | ||||
| 	throw std::runtime_error(errorString); | ||||
| } | ||||
|  | ||||
| CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::string & objectName) const | ||||
| { | ||||
| 	std::string subtype = "object"; //Default for objects with no subIds | ||||
| 	std::string type; | ||||
|  | ||||
| 	auto scopeAndFullName = vstd::splitStringToPair(objectName, ':'); | ||||
| 	logGlobal->debug("scopeAndFullName: %s, %s", scopeAndFullName.first, scopeAndFullName.second); | ||||
| 	 | ||||
| 	auto typeAndName = vstd::splitStringToPair(scopeAndFullName.second, '.'); | ||||
| 	logGlobal->debug("typeAndName: %s, %s", typeAndName.first, typeAndName.second); | ||||
| 	 | ||||
| 	auto nameAndSubtype = vstd::splitStringToPair(typeAndName.second, '.'); | ||||
| 	logGlobal->debug("nameAndSubtype: %s, %s", nameAndSubtype.first, nameAndSubtype.second); | ||||
|  | ||||
| 	if (!nameAndSubtype.first.empty()) | ||||
| 	{ | ||||
| 		type = nameAndSubtype.first; | ||||
| 		subtype = nameAndSubtype.second; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		type = typeAndName.second; | ||||
| 	} | ||||
| 	 | ||||
| 	return getCompoundIdentifier(boost::to_lower_copy(scopeAndFullName.first), type, subtype); | ||||
| } | ||||
|  | ||||
| std::set<MapObjectID> CObjectClassesHandler::knownObjects() const | ||||
| { | ||||
| 	std::set<MapObjectID> ret; | ||||
| @@ -459,6 +517,18 @@ void CObjectClassesHandler::afterLoadFinalization() | ||||
| 				logGlobal->warn("No templates found for %s:%s", entry->getJsonKey(), obj->getJsonKey()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for(auto & entry : objectIdHandlers) | ||||
| 	{ | ||||
| 		// Call function for each object id | ||||
| 		entry.second(entry.first); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CObjectClassesHandler::resolveObjectCompoundId(const std::string & id, std::function<void(CompoundMapObjectID)> callback) | ||||
| { | ||||
| 	auto compoundId = getCompoundIdentifier(id); | ||||
| 	objectIdHandlers.push_back(std::make_pair(compoundId, callback)); | ||||
| } | ||||
|  | ||||
| void CObjectClassesHandler::generateExtraMonolithsForRMG(ObjectClass * container) | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
| #include "../constants/EntityIdentifiers.h" | ||||
| #include "../mapObjects/CompoundMapObjectID.h" | ||||
| #include "../IHandlerBase.h" | ||||
| #include "../json/JsonNode.h" | ||||
|  | ||||
| @@ -19,27 +19,6 @@ class AObjectTypeHandler; | ||||
| class ObjectTemplate; | ||||
| struct SObjectSounds; | ||||
|  | ||||
| struct DLL_LINKAGE CompoundMapObjectID | ||||
| { | ||||
| 	si32 primaryID; | ||||
| 	si32 secondaryID; | ||||
|  | ||||
| 	CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {}; | ||||
|  | ||||
| 	bool operator<(const CompoundMapObjectID& other) const | ||||
| 	{ | ||||
| 		if(this->primaryID != other.primaryID) | ||||
| 			return this->primaryID < other.primaryID; | ||||
| 		else | ||||
| 			return this->secondaryID < other.secondaryID; | ||||
| 	} | ||||
|  | ||||
| 	bool operator==(const CompoundMapObjectID& other) const | ||||
| 	{ | ||||
| 		return (this->primaryID == other.primaryID) && (this->secondaryID == other.secondaryID); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| class CGObjectInstance; | ||||
|  | ||||
| using TObjectTypeHandler = std::shared_ptr<AObjectTypeHandler>; | ||||
| @@ -74,6 +53,8 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase, boost::noncopyabl | ||||
| 	/// map that is filled during construction with all known handlers. Not serializeable due to usage of std::function | ||||
| 	std::map<std::string, std::function<TObjectTypeHandler()> > handlerConstructors; | ||||
|  | ||||
| 	std::vector<std::pair<CompoundMapObjectID, std::function<void(CompoundMapObjectID)>>> objectIdHandlers; | ||||
|  | ||||
| 	/// container with H3 templates, used only during loading, no need to serialize it | ||||
| 	using TTemplatesContainer = std::multimap<std::pair<MapObjectID, MapObjectSubID>, std::shared_ptr<const ObjectTemplate>>; | ||||
| 	TTemplatesContainer legacyTemplates; | ||||
| @@ -110,15 +91,19 @@ public: | ||||
| 	TObjectTypeHandler getHandlerFor(MapObjectID type, MapObjectSubID subtype) const; | ||||
| 	TObjectTypeHandler getHandlerFor(const std::string & scope, const std::string & type, const std::string & subtype) const; | ||||
| 	TObjectTypeHandler getHandlerFor(CompoundMapObjectID compoundIdentifier) const; | ||||
| 	CompoundMapObjectID getCompoundIdentifier(const std::string & scope, const std::string & type, const std::string & subtype) const; | ||||
| 	CompoundMapObjectID getCompoundIdentifier(const std::string & objectName) const; | ||||
|  | ||||
| 	std::string getObjectName(MapObjectID type, MapObjectSubID subtype) const; | ||||
|  | ||||
| 	SObjectSounds getObjectSounds(MapObjectID type, MapObjectSubID subtype) const; | ||||
|  | ||||
| 	void resolveObjectCompoundId(const std::string & id, std::function<void(CompoundMapObjectID)> callback); | ||||
|  | ||||
| 	/// Returns handler string describing the handler (for use in client) | ||||
| 	std::string getObjectHandlerName(MapObjectID type) const; | ||||
|  | ||||
| 	std::string getJsonKey(MapObjectID type) const; | ||||
| }; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
| VCMI_LIB_NAMESPACE_END | ||||
| @@ -49,7 +49,10 @@ public: | ||||
|  | ||||
| 	virtual bool givesBonuses() const { return false; } | ||||
|  | ||||
| 	virtual bool hasGuards() const { return false; } | ||||
|  | ||||
| 	virtual ~IObjectInfo() = default; | ||||
|  | ||||
| }; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|   | ||||
							
								
								
									
										37
									
								
								lib/mapObjects/CompoundMapObjectID.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								lib/mapObjects/CompoundMapObjectID.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| /* | ||||
|  * CompoundMapObjectID.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
| #include "../constants/EntityIdentifiers.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| struct DLL_LINKAGE CompoundMapObjectID | ||||
| { | ||||
| 	si32 primaryID; | ||||
| 	si32 secondaryID; | ||||
|  | ||||
| 	CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {}; | ||||
|  | ||||
| 	bool operator<(const CompoundMapObjectID& other) const | ||||
| 	{ | ||||
| 		if(this->primaryID != other.primaryID) | ||||
| 			return this->primaryID < other.primaryID; | ||||
| 		else | ||||
| 			return this->secondaryID < other.secondaryID; | ||||
| 	} | ||||
|  | ||||
| 	bool operator==(const CompoundMapObjectID& other) const | ||||
| 	{ | ||||
| 		return (this->primaryID == other.primaryID) && (this->secondaryID == other.secondaryID); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
| @@ -508,6 +508,11 @@ bool ObjectTemplate::canBePlacedAt(TerrainId terrainID) const | ||||
| 	return vstd::contains(allowedTerrains, terrainID); | ||||
| } | ||||
|  | ||||
| CompoundMapObjectID ObjectTemplate::getCompoundID() const | ||||
| { | ||||
| 	return CompoundMapObjectID(id, subid); | ||||
| } | ||||
|  | ||||
| void ObjectTemplate::recalculate() | ||||
| { | ||||
| 	calculateWidth(); | ||||
|   | ||||
| @@ -13,6 +13,7 @@ | ||||
| #include "../int3.h" | ||||
| #include "../filesystem/ResourcePath.h" | ||||
| #include "../serializer/Serializeable.h" | ||||
| #include "../mapObjects/CompoundMapObjectID.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| @@ -46,6 +47,7 @@ public: | ||||
| 	/// H3 ID/subID of this object | ||||
| 	MapObjectID id; | ||||
| 	MapObjectSubID subid; | ||||
|  | ||||
| 	/// print priority, objects with higher priority will be print first, below everything else | ||||
| 	si32 printPriority; | ||||
| 	/// animation file that should be used to display object | ||||
| @@ -122,6 +124,8 @@ public: | ||||
| 	// Checks if object can be placed on specific terrain | ||||
| 	bool canBePlacedAt(TerrainId terrain) const; | ||||
|  | ||||
| 	CompoundMapObjectID getCompoundID() const; | ||||
|  | ||||
| 	ObjectTemplate(); | ||||
|  | ||||
| 	void readTxt(CLegacyConfigParser & parser); | ||||
|   | ||||
| @@ -526,6 +526,11 @@ bool Rewardable::Info::givesBonuses() const | ||||
| 	return testForKey(parameters, "bonuses"); | ||||
| } | ||||
|  | ||||
| bool Rewardable::Info::hasGuards() const | ||||
| { | ||||
| 	return testForKey(parameters, "guards"); | ||||
| } | ||||
|  | ||||
| const JsonNode & Rewardable::Info::getParameters() const | ||||
| { | ||||
| 	return parameters; | ||||
|   | ||||
| @@ -68,6 +68,8 @@ public: | ||||
|  | ||||
| 	bool givesBonuses() const override; | ||||
|  | ||||
| 	bool hasGuards() const override; | ||||
|  | ||||
| 	void configureObject(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb) const; | ||||
|  | ||||
| 	void init(const JsonNode & objectConfig, const std::string & objectTextID); | ||||
|   | ||||
| @@ -102,6 +102,7 @@ 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); | ||||
| } | ||||
|  | ||||
| ZoneOptions::ZoneOptions(): | ||||
| @@ -156,7 +157,7 @@ std::optional<int> ZoneOptions::getOwner() const | ||||
| 	return owner; | ||||
| } | ||||
|  | ||||
| const std::set<TerrainId> ZoneOptions::getTerrainTypes() const | ||||
| std::set<TerrainId> ZoneOptions::getTerrainTypes() const | ||||
| { | ||||
| 	if (terrainTypes.empty()) | ||||
| 	{ | ||||
| @@ -191,7 +192,7 @@ std::set<FactionID> ZoneOptions::getDefaultTownTypes() const | ||||
| 	return VLC->townh->getDefaultAllowed(); | ||||
| } | ||||
|  | ||||
| const std::set<FactionID> ZoneOptions::getTownTypes() const | ||||
| std::set<FactionID> ZoneOptions::getTownTypes() const | ||||
| { | ||||
| 	if (townTypes.empty()) | ||||
| 	{ | ||||
| @@ -214,7 +215,7 @@ void ZoneOptions::setMonsterTypes(const std::set<FactionID> & value) | ||||
| 	monsterTypes = value; | ||||
| } | ||||
|  | ||||
| const std::set<FactionID> ZoneOptions::getMonsterTypes() const | ||||
| std::set<FactionID> ZoneOptions::getMonsterTypes() const | ||||
| { | ||||
| 	return vstd::difference(monsterTypes, bannedMonsters); | ||||
| } | ||||
| @@ -250,7 +251,7 @@ void ZoneOptions::addTreasureInfo(const CTreasureInfo & value) | ||||
| 	vstd::amax(maxTreasureValue, value.max); | ||||
| } | ||||
|  | ||||
| const std::vector<CTreasureInfo> & ZoneOptions::getTreasureInfo() const | ||||
| std::vector<CTreasureInfo> ZoneOptions::getTreasureInfo() const | ||||
| { | ||||
| 	return treasureInfo; | ||||
| } | ||||
| @@ -272,7 +273,22 @@ TRmgTemplateZoneId ZoneOptions::getTerrainTypeLikeZone() const | ||||
|  | ||||
| TRmgTemplateZoneId ZoneOptions::getTreasureLikeZone() const | ||||
| { | ||||
|     return treasureLikeZone; | ||||
| 	return treasureLikeZone; | ||||
| } | ||||
|  | ||||
| ObjectConfig ZoneOptions::getCustomObjects() const | ||||
| { | ||||
| 	return objectConfig; | ||||
| } | ||||
|  | ||||
| void ZoneOptions::setCustomObjects(const ObjectConfig & value) | ||||
| { | ||||
| 	objectConfig = value; | ||||
| } | ||||
|  | ||||
| TRmgTemplateZoneId ZoneOptions::getCustomObjectsLikeZone() const | ||||
| { | ||||
| 	return customObjectsLikeZone; | ||||
| } | ||||
|  | ||||
| void ZoneOptions::addConnection(const ZoneConnection & connection) | ||||
| @@ -334,6 +350,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) | ||||
| 	SERIALIZE_ZONE_LINK(minesLikeZone); | ||||
| 	SERIALIZE_ZONE_LINK(terrainTypeLikeZone); | ||||
| 	SERIALIZE_ZONE_LINK(treasureLikeZone); | ||||
| 	SERIALIZE_ZONE_LINK(customObjectsLikeZone); | ||||
|  | ||||
| 	#undef SERIALIZE_ZONE_LINK | ||||
|  | ||||
| @@ -398,6 +415,8 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) | ||||
| 			handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], mines[idx], 0); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	handler.serializeStruct("customObjects", objectConfig); | ||||
| } | ||||
|  | ||||
| ZoneConnection::ZoneConnection(): | ||||
| @@ -759,53 +778,29 @@ const JsonNode & CRmgTemplate::getMapSettings() const | ||||
| 	return *mapSettings; | ||||
| } | ||||
|  | ||||
| std::set<TerrainId> CRmgTemplate::inheritTerrainType(std::shared_ptr<ZoneOptions> zone, uint32_t iteration /* = 0 */) | ||||
| template<typename T> | ||||
| T CRmgTemplate::inheritZoneProperty(std::shared_ptr<rmg::ZoneOptions> zone,  | ||||
| 									T (rmg::ZoneOptions::*getter)() const, | ||||
| 									void (rmg::ZoneOptions::*setter)(const T&), | ||||
| 									TRmgTemplateZoneId (rmg::ZoneOptions::*inheritFrom)() const, | ||||
| 									const std::string& propertyString, | ||||
| 									uint32_t iteration) | ||||
| { | ||||
| 	if (iteration >= 50) | ||||
| 	{ | ||||
| 		logGlobal->error("Infinite recursion for terrain types detected in template %s", name); | ||||
| 		return std::set<TerrainId>(); | ||||
| 		logGlobal->error("Infinite recursion for %s detected in template %s", propertyString, name); | ||||
| 		return T(); | ||||
| 	} | ||||
| 	if (zone->getTerrainTypeLikeZone() != ZoneOptions::NO_ZONE) | ||||
| 	 | ||||
| 	if (((*zone).*inheritFrom)() != rmg::ZoneOptions::NO_ZONE) | ||||
| 	{ | ||||
| 		iteration++; | ||||
| 		const auto otherZone = zones.at(zone->getTerrainTypeLikeZone()); | ||||
| 		zone->setTerrainTypes(inheritTerrainType(otherZone, iteration)); | ||||
| 		const auto otherZone = zones.at(((*zone).*inheritFrom)()); | ||||
| 		T inheritedValue = inheritZoneProperty(otherZone, getter, setter, inheritFrom, propertyString, iteration); | ||||
| 		((*zone).*setter)(inheritedValue); | ||||
| 	} | ||||
| 	//This implicitly excludes banned terrains | ||||
| 	return zone->getTerrainTypes(); | ||||
| } | ||||
|  | ||||
| std::map<TResource, ui16> CRmgTemplate::inheritMineTypes(std::shared_ptr<ZoneOptions> zone, uint32_t iteration /* = 0 */) | ||||
| { | ||||
| 	if (iteration >= 50) | ||||
| 	{ | ||||
| 		logGlobal->error("Infinite recursion for mine types detected in template %s", name); | ||||
| 		return std::map<TResource, ui16>(); | ||||
| 	} | ||||
| 	if (zone->getMinesLikeZone() != ZoneOptions::NO_ZONE) | ||||
| 	{ | ||||
| 		iteration++; | ||||
| 		const auto otherZone = zones.at(zone->getMinesLikeZone()); | ||||
| 		zone->setMinesInfo(inheritMineTypes(otherZone, iteration)); | ||||
| 	} | ||||
| 	return zone->getMinesInfo(); | ||||
| } | ||||
|  | ||||
| std::vector<CTreasureInfo> CRmgTemplate::inheritTreasureInfo(std::shared_ptr<ZoneOptions> zone, uint32_t iteration /* = 0 */) | ||||
| { | ||||
| 	if (iteration >= 50) | ||||
| 	{ | ||||
| 		logGlobal->error("Infinite recursion for treasures detected in template %s", name); | ||||
| 		return std::vector<CTreasureInfo>(); | ||||
| 	} | ||||
| 	if (zone->getTreasureLikeZone() != ZoneOptions::NO_ZONE) | ||||
| 	{ | ||||
| 		iteration++; | ||||
| 		const auto otherZone = zones.at(zone->getTreasureLikeZone()); | ||||
| 		zone->setTreasureInfo(inheritTreasureInfo(otherZone, iteration)); | ||||
| 	} | ||||
| 	return zone->getTreasureInfo(); | ||||
| 	 | ||||
| 	return ((*zone).*getter)(); | ||||
| } | ||||
|  | ||||
| void CRmgTemplate::afterLoad() | ||||
| @@ -814,12 +809,32 @@ void CRmgTemplate::afterLoad() | ||||
| 	{ | ||||
| 		auto zone = idAndZone.second; | ||||
|  | ||||
| 		//Inherit properties recursively. | ||||
| 		inheritTerrainType(zone); | ||||
| 		inheritMineTypes(zone); | ||||
| 		inheritTreasureInfo(zone); | ||||
| 		// Inherit properties recursively | ||||
| 		inheritZoneProperty(zone,  | ||||
| 							&rmg::ZoneOptions::getTerrainTypes,  | ||||
| 							&rmg::ZoneOptions::setTerrainTypes,  | ||||
| 							&rmg::ZoneOptions::getTerrainTypeLikeZone, | ||||
| 							"terrain types"); | ||||
| 		 | ||||
| 		inheritZoneProperty(zone,  | ||||
| 							&rmg::ZoneOptions::getMinesInfo,  | ||||
| 							&rmg::ZoneOptions::setMinesInfo,  | ||||
| 							&rmg::ZoneOptions::getMinesLikeZone, | ||||
| 							"mine types"); | ||||
| 		 | ||||
| 		inheritZoneProperty(zone,  | ||||
| 							&rmg::ZoneOptions::getTreasureInfo,  | ||||
| 							&rmg::ZoneOptions::setTreasureInfo,  | ||||
| 							&rmg::ZoneOptions::getTreasureLikeZone, | ||||
| 							"treasure info"); | ||||
|  | ||||
| 		//TODO: Inherit monster types as well | ||||
| 		inheritZoneProperty(zone,  | ||||
| 							&rmg::ZoneOptions::getCustomObjects,  | ||||
| 							&rmg::ZoneOptions::setCustomObjects,  | ||||
| 							&rmg::ZoneOptions::getCustomObjectsLikeZone, | ||||
| 							"custom objects"); | ||||
|  | ||||
| 				//TODO: Inherit monster types as well | ||||
| 		auto monsterTypes = zone->getMonsterTypes(); | ||||
| 		if (monsterTypes.empty()) | ||||
| 		{ | ||||
| @@ -848,6 +863,7 @@ void CRmgTemplate::afterLoad() | ||||
| 	allowedWaterContent.erase(EWaterContent::RANDOM); | ||||
| } | ||||
|  | ||||
| // TODO: Allow any integer size which does not match enum, as well | ||||
| void CRmgTemplate::serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName) | ||||
| { | ||||
| 	static const std::map<std::string, int3> sizeMapping = | ||||
| @@ -916,5 +932,19 @@ void CRmgTemplate::serializePlayers(JsonSerializeFormat & handler, CPlayerCountR | ||||
| 		value.fromString(encodedValue); | ||||
| } | ||||
|  | ||||
| const std::vector<CompoundMapObjectID> & ZoneOptions::getBannedObjects() const | ||||
| { | ||||
| 	return objectConfig.getBannedObjects(); | ||||
| } | ||||
|  | ||||
| const std::vector<ObjectConfig::EObjectCategory> & ZoneOptions::getBannedObjectCategories() const | ||||
| { | ||||
| 	return objectConfig.getBannedObjectCategories(); | ||||
| } | ||||
|  | ||||
| const std::vector<ObjectInfo> & ZoneOptions::getConfiguredObjects() const | ||||
| { | ||||
| 	return objectConfig.getConfiguredObjects(); | ||||
| } | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|   | ||||
| @@ -13,10 +13,14 @@ | ||||
| #include "../int3.h" | ||||
| #include "../GameConstants.h" | ||||
| #include "../ResourceSet.h" | ||||
| #include "ObjectInfo.h" | ||||
| #include "ObjectConfig.h" | ||||
| #include "../mapObjectConstructors/CObjectClassesHandler.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| class JsonSerializeFormat; | ||||
| struct CompoundMapObjectID; | ||||
|  | ||||
| enum class ETemplateZoneType | ||||
| { | ||||
| @@ -132,6 +136,9 @@ public: | ||||
| 		int castleCount; | ||||
| 		int townDensity; | ||||
| 		int castleDensity; | ||||
|  | ||||
| 		// TODO: Copy from another zone once its randomized | ||||
| 		TRmgTemplateZoneId sourceZone = NO_ZONE; | ||||
| 	}; | ||||
|  | ||||
| 	ZoneOptions(); | ||||
| @@ -146,15 +153,15 @@ public: | ||||
| 	void setSize(int value); | ||||
| 	std::optional<int> getOwner() const; | ||||
|  | ||||
| 	const std::set<TerrainId> getTerrainTypes() const; | ||||
| 	std::set<TerrainId> getTerrainTypes() const; | ||||
| 	void setTerrainTypes(const std::set<TerrainId> & value); | ||||
| 	std::set<TerrainId> getDefaultTerrainTypes() const; | ||||
|  | ||||
| 	const CTownInfo & getPlayerTowns() const; | ||||
| 	const CTownInfo & getNeutralTowns() const; | ||||
| 	std::set<FactionID> getDefaultTownTypes() const; | ||||
| 	const std::set<FactionID> getTownTypes() const; | ||||
| 	const std::set<FactionID> getMonsterTypes() const; | ||||
| 	std::set<FactionID> getTownTypes() const; | ||||
| 	std::set<FactionID> getMonsterTypes() const; | ||||
|  | ||||
| 	void setTownTypes(const std::set<FactionID> & value); | ||||
| 	void setMonsterTypes(const std::set<FactionID> & value); | ||||
| @@ -164,7 +171,7 @@ public: | ||||
|  | ||||
| 	void setTreasureInfo(const std::vector<CTreasureInfo> & value); | ||||
| 	void addTreasureInfo(const CTreasureInfo & value); | ||||
| 	const std::vector<CTreasureInfo> & getTreasureInfo() const; | ||||
| 	std::vector<CTreasureInfo> getTreasureInfo() const; | ||||
| 	ui32 getMaxTreasureValue() const; | ||||
| 	void recalculateMaxTreasureValue(); | ||||
|  | ||||
| @@ -183,12 +190,24 @@ public: | ||||
| 	bool areTownsSameType() const; | ||||
| 	bool isMatchTerrainToTown() const; | ||||
|  | ||||
| 	// Get a group of configured objects | ||||
| 	const std::vector<CompoundMapObjectID> & getBannedObjects() const; | ||||
| 	const std::vector<ObjectConfig::EObjectCategory> & getBannedObjectCategories() const; | ||||
| 	const std::vector<ObjectInfo> & getConfiguredObjects() const; | ||||
|  | ||||
| 	// Copy whole custom object config from another zone | ||||
| 	ObjectConfig getCustomObjects() const; | ||||
| 	void setCustomObjects(const ObjectConfig & value); | ||||
| 	TRmgTemplateZoneId	getCustomObjectsLikeZone() const; | ||||
|  | ||||
| protected: | ||||
| 	TRmgTemplateZoneId id; | ||||
| 	ETemplateZoneType type; | ||||
| 	int size; | ||||
| 	ui32 maxTreasureValue; | ||||
| 	std::optional<int> owner; | ||||
|  | ||||
| 	ObjectConfig objectConfig; | ||||
| 	CTownInfo playerTowns; | ||||
| 	CTownInfo neutralTowns; | ||||
| 	bool matchTerrainToTown; | ||||
| @@ -211,6 +230,7 @@ protected: | ||||
| 	TRmgTemplateZoneId minesLikeZone; | ||||
| 	TRmgTemplateZoneId terrainTypeLikeZone; | ||||
| 	TRmgTemplateZoneId treasureLikeZone; | ||||
| 	TRmgTemplateZoneId customObjectsLikeZone; | ||||
| }; | ||||
|  | ||||
| } | ||||
| @@ -280,8 +300,21 @@ private: | ||||
| 	std::set<TerrainId> inheritTerrainType(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0); | ||||
| 	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 serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName); | ||||
| 	void serializePlayers(JsonSerializeFormat & handler, CPlayerCountRange & value, const std::string & fieldName); | ||||
|  | ||||
| 	template<typename T> | ||||
| 	T inheritZoneProperty(std::shared_ptr<rmg::ZoneOptions> zone,  | ||||
| 						  T (rmg::ZoneOptions::*getter)() const, | ||||
| 						  void (rmg::ZoneOptions::*setter)(const T&), | ||||
| 						  TRmgTemplateZoneId (rmg::ZoneOptions::*inheritFrom)() const, | ||||
| 						  const std::string& propertyString, | ||||
| 						  uint32_t iteration = 0); | ||||
|  | ||||
| }; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
| VCMI_LIB_NAMESPACE_END | ||||
							
								
								
									
										189
									
								
								lib/rmg/ObjectConfig.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								lib/rmg/ObjectConfig.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | ||||
| /* | ||||
|  * ObjectConfig.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #include "StdInc.h" | ||||
| #include <boost/bimap.hpp> | ||||
| #include <boost/assign.hpp> | ||||
| #include "ObjectInfo.h" | ||||
| #include "ObjectConfig.h" | ||||
|  | ||||
| #include "../VCMI_Lib.h" | ||||
| #include "../mapObjectConstructors/CObjectClassesHandler.h" | ||||
| #include "../mapObjectConstructors/AObjectTypeHandler.h" | ||||
| #include "../serializer/JsonSerializeFormat.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| void ObjectConfig::addBannedObject(const CompoundMapObjectID & objid) | ||||
| { | ||||
| 	// FIXME: We do not need to store the object info, just the id | ||||
|  | ||||
| 	bannedObjects.push_back(objid); | ||||
|  | ||||
| 	logGlobal->info("Banned object of type %d.%d", objid.primaryID, objid.secondaryID); | ||||
| } | ||||
|  | ||||
| void ObjectConfig::addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid) | ||||
| { | ||||
| 	customObjects.push_back(object); | ||||
| 	auto & lastObject = customObjects.back(); | ||||
| 	lastObject.setAllTemplates(objid.primaryID, objid.secondaryID); | ||||
|  | ||||
| 	assert(lastObject.templates.size() > 0); | ||||
| 	logGlobal->info("Added custom object of type %d.%d", objid.primaryID, objid.secondaryID); | ||||
| } | ||||
|  | ||||
| void ObjectConfig::serializeJson(JsonSerializeFormat & handler) | ||||
| { | ||||
| 	// TODO: We need serializer utility for list of enum values | ||||
|  | ||||
| 	static const boost::bimap<EObjectCategory, std::string> OBJECT_CATEGORY_STRINGS = boost::assign::list_of<boost::bimap<EObjectCategory, std::string>::relation> | ||||
| 		(EObjectCategory::OTHER, "other") | ||||
| 		(EObjectCategory::ALL, "all") | ||||
| 		(EObjectCategory::NONE, "none") | ||||
| 		(EObjectCategory::CREATURE_BANK, "creatureBank") | ||||
| 		(EObjectCategory::BONUS, "bonus") | ||||
| 		(EObjectCategory::DWELLING, "dwelling") | ||||
| 		(EObjectCategory::RESOURCE, "resource") | ||||
| 		(EObjectCategory::RESOURCE_GENERATOR, "resourceGenerator") | ||||
| 		(EObjectCategory::SPELL_SCROLL, "spellScroll") | ||||
| 		(EObjectCategory::RANDOM_ARTIFACT, "randomArtifact") | ||||
| 		(EObjectCategory::PANDORAS_BOX, "pandorasBox") | ||||
| 		(EObjectCategory::QUEST_ARTIFACT, "questArtifact") | ||||
| 		(EObjectCategory::SEER_HUT, "seerHut"); | ||||
|  | ||||
|  | ||||
| 	// TODO: Separate into individual methods to enforce RAII destruction? | ||||
| 	{ | ||||
| 		auto categories = handler.enterArray("bannedCategories"); | ||||
| 		if (handler.saving) | ||||
| 		{ | ||||
| 			for (const auto& category : bannedObjectCategories) | ||||
| 			{ | ||||
| 				auto str = OBJECT_CATEGORY_STRINGS.left.at(category); | ||||
| 				categories.serializeString(categories.size(), str); | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			std::vector<std::string> categoryNames; | ||||
| 			categories.serializeArray(categoryNames); | ||||
|  | ||||
| 			for (const auto & categoryName : categoryNames) | ||||
| 			{ | ||||
| 				auto it = OBJECT_CATEGORY_STRINGS.right.find(categoryName); | ||||
| 				if (it != OBJECT_CATEGORY_STRINGS.right.end()) | ||||
| 				{ | ||||
| 					bannedObjectCategories.push_back(it->second); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// FIXME: Doesn't seem to use this field at all | ||||
| 	 | ||||
| 	{ | ||||
| 		auto bannedObjectData = handler.enterArray("bannedObjects");	 | ||||
| 		if (handler.saving) | ||||
| 		{ | ||||
|  | ||||
| 			// FIXME: Do we even need to serialize / store banned objects? | ||||
| 			/* | ||||
| 			for (const auto & object : bannedObjects) | ||||
| 			{ | ||||
| 				// TODO: Translate id back to string? | ||||
|  | ||||
|  | ||||
| 				JsonNode node; | ||||
| 				node.String() = VLC->objtypeh->getHandlerFor(object.primaryID, object.secondaryID); | ||||
| 				// TODO: Check if AI-generated code is right | ||||
|  | ||||
|  | ||||
| 			} | ||||
| 			// handler.serializeRaw("bannedObjects", node, std::nullopt); | ||||
|  | ||||
| 			*/ | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			std::vector<std::string> objectNames; | ||||
| 			bannedObjectData.serializeArray(objectNames); | ||||
|  | ||||
| 			for (const auto & objectName : objectNames) | ||||
| 			{ | ||||
| 				VLC->objtypeh->resolveObjectCompoundId(objectName, | ||||
| 					[this](CompoundMapObjectID objid) | ||||
| 					{ | ||||
| 						addBannedObject(objid); | ||||
| 					} | ||||
| 				); | ||||
| 				 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	auto commonObjectData = handler.getCurrent()["commonObjects"].Vector();	 | ||||
| 	if (handler.saving) | ||||
| 	{ | ||||
|  | ||||
| 		//TODO? | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		for (const auto & objectConfig : commonObjectData) | ||||
| 		{ | ||||
| 			auto objectName = objectConfig["id"].String(); | ||||
| 			auto rmg = objectConfig["rmg"].Struct(); | ||||
|  | ||||
| 			// TODO: Use common code with default rmg config | ||||
| 			auto objectValue = rmg["value"].Integer(); | ||||
| 			auto objectProbability = rmg["rarity"].Integer(); | ||||
|  | ||||
| 			auto objectMaxPerZone = rmg["zoneLimit"].Integer(); | ||||
| 			if (objectMaxPerZone == 0) | ||||
| 			{ | ||||
| 				objectMaxPerZone = std::numeric_limits<int>::max(); | ||||
| 			} | ||||
|  | ||||
| 			VLC->objtypeh->resolveObjectCompoundId(objectName, | ||||
|  | ||||
| 				[this, objectValue, objectProbability, objectMaxPerZone](CompoundMapObjectID objid) | ||||
| 				{ | ||||
| 					ObjectInfo object(objid.primaryID, objid.secondaryID); | ||||
| 					 | ||||
| 					// TODO: Configure basic generateObject function | ||||
|  | ||||
| 					object.value = objectValue; | ||||
| 					object.probability = objectProbability; | ||||
| 					object.maxPerZone = objectMaxPerZone; | ||||
| 					addCustomObject(object, objid); | ||||
| 				} | ||||
| 			); | ||||
| 			 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| const std::vector<ObjectInfo> & ObjectConfig::getConfiguredObjects() const | ||||
| { | ||||
| 	return customObjects; | ||||
| } | ||||
|  | ||||
| const std::vector<CompoundMapObjectID> & ObjectConfig::getBannedObjects() const | ||||
| { | ||||
| 	return bannedObjects; | ||||
| } | ||||
|  | ||||
| const std::vector<ObjectConfig::EObjectCategory> & ObjectConfig::getBannedObjectCategories() const | ||||
| { | ||||
| 	return bannedObjectCategories; | ||||
| } | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
							
								
								
									
										57
									
								
								lib/rmg/ObjectConfig.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								lib/rmg/ObjectConfig.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| /* | ||||
|  * ObjectInfo.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "../mapObjects/CompoundMapObjectID.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| class DLL_LINKAGE ObjectConfig | ||||
| { | ||||
| public: | ||||
|  | ||||
| 	enum class EObjectCategory | ||||
| 	{ | ||||
| 		OTHER = -2, | ||||
| 		ALL = -1, | ||||
| 		NONE = 0, | ||||
| 		CREATURE_BANK = 1, | ||||
| 		BONUS, | ||||
| 		DWELLING, | ||||
| 		RESOURCE, | ||||
| 		RESOURCE_GENERATOR, | ||||
| 		SPELL_SCROLL, | ||||
| 		RANDOM_ARTIFACT, | ||||
| 		PANDORAS_BOX, | ||||
| 		QUEST_ARTIFACT, | ||||
| 		SEER_HUT | ||||
| 	}; | ||||
|  | ||||
| 	void addBannedObject(const CompoundMapObjectID & objid); | ||||
| 	void addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid); | ||||
| 	void clearBannedObjects(); | ||||
| 	void clearCustomObjects(); | ||||
| 	const std::vector<CompoundMapObjectID> & getBannedObjects() const; | ||||
| 	const std::vector<EObjectCategory> & getBannedObjectCategories() const; | ||||
| 	const std::vector<ObjectInfo> & getConfiguredObjects() const; | ||||
|  | ||||
| 	void serializeJson(JsonSerializeFormat & handler); | ||||
| private: | ||||
| 	// TODO: Add convenience method for banning objects by name | ||||
| 	std::vector<CompoundMapObjectID> bannedObjects; | ||||
| 	std::vector<EObjectCategory> bannedObjectCategories; | ||||
|  | ||||
| 	// TODO: In what format should I store custom objects? | ||||
| 	// Need to convert map serialization format to ObjectInfo | ||||
| 	std::vector<ObjectInfo> customObjects; | ||||
| }; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
							
								
								
									
										85
									
								
								lib/rmg/ObjectInfo.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								lib/rmg/ObjectInfo.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| /* | ||||
|  * ObjectInfo.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #include "StdInc.h" | ||||
| #include "ObjectInfo.h" | ||||
|  | ||||
| #include "../VCMI_Lib.h" | ||||
| #include "../mapObjectConstructors/CObjectClassesHandler.h" | ||||
| #include "../mapObjectConstructors/AObjectTypeHandler.h" | ||||
| #include "../serializer/JsonSerializeFormat.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| ObjectInfo::ObjectInfo(si32 ID, si32 subID): | ||||
| 	primaryID(ID), | ||||
| 	secondaryID(subID), | ||||
| 	destroyObject([](CGObjectInstance * obj){}), | ||||
| 	maxPerZone(std::numeric_limits<ui32>::max()) | ||||
| { | ||||
| } | ||||
|  | ||||
| ObjectInfo::ObjectInfo(CompoundMapObjectID id): | ||||
| 	ObjectInfo(id.primaryID, id.secondaryID) | ||||
| { | ||||
| } | ||||
|  | ||||
| ObjectInfo::ObjectInfo(const ObjectInfo & other) | ||||
| { | ||||
| 	templates = other.templates; | ||||
| 	primaryID = other.primaryID; | ||||
| 	secondaryID = other.secondaryID; | ||||
| 	value = other.value; | ||||
| 	probability = other.probability; | ||||
| 	maxPerZone = other.maxPerZone; | ||||
| 	generateObject = other.generateObject; | ||||
| 	destroyObject = other.destroyObject; | ||||
| } | ||||
|  | ||||
| ObjectInfo & ObjectInfo::operator=(const ObjectInfo & other) | ||||
| { | ||||
| 	if (this == &other) | ||||
| 		return *this; | ||||
|  | ||||
| 	templates = other.templates; | ||||
| 	primaryID = other.primaryID; | ||||
| 	secondaryID = other.secondaryID; | ||||
| 	value = other.value; | ||||
| 	probability = other.probability; | ||||
| 	maxPerZone = other.maxPerZone; | ||||
| 	generateObject = other.generateObject; | ||||
| 	destroyObject = other.destroyObject; | ||||
| 	return *this; | ||||
| } | ||||
|  | ||||
| void ObjectInfo::setAllTemplates(MapObjectID type, MapObjectSubID subtype) | ||||
| { | ||||
| 	auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype); | ||||
| 	if(!templHandler) | ||||
| 		return; | ||||
| 	 | ||||
| 	templates = templHandler->getTemplates(); | ||||
| } | ||||
|  | ||||
| void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrainType) | ||||
| { | ||||
| 	auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype); | ||||
| 	if(!templHandler) | ||||
| 		return; | ||||
| 	 | ||||
| 	templates = templHandler->getTemplates(terrainType); | ||||
| } | ||||
|  | ||||
| CompoundMapObjectID ObjectInfo::getCompoundID() const | ||||
| { | ||||
| 	return CompoundMapObjectID(primaryID, secondaryID); | ||||
| } | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
							
								
								
									
										45
									
								
								lib/rmg/ObjectInfo.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								lib/rmg/ObjectInfo.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| /* | ||||
|  * ObjectInfo.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "../mapObjects/ObjectTemplate.h" | ||||
| #include "../mapObjects/CompoundMapObjectID.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| struct CompoundMapObjectID; | ||||
| class CGObjectInstance; | ||||
|  | ||||
| struct DLL_LINKAGE ObjectInfo | ||||
| { | ||||
| 	ObjectInfo(si32 ID, si32 subID); | ||||
| 	ObjectInfo(CompoundMapObjectID id); | ||||
| 	ObjectInfo(const ObjectInfo & other); | ||||
| 	ObjectInfo & operator=(const ObjectInfo & other); | ||||
|  | ||||
| 	std::vector<std::shared_ptr<const ObjectTemplate>> templates; | ||||
| 	si32 primaryID; | ||||
| 	si32 secondaryID; | ||||
| 	ui32 value = 0; | ||||
| 	ui16 probability = 0; | ||||
| 	ui32 maxPerZone = 1; | ||||
| 	//ui32 maxPerMap; //unused | ||||
| 	std::function<CGObjectInstance *()> generateObject; | ||||
| 	std::function<void(CGObjectInstance *)> destroyObject; | ||||
| 	 | ||||
| 	void setAllTemplates(MapObjectID type, MapObjectSubID subtype); | ||||
| 	void setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrain); | ||||
|  | ||||
| 	CompoundMapObjectID getCompoundID() const; | ||||
| 	//bool matchesId(const CompoundMapObjectID & id) const; | ||||
| }; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
| @@ -45,7 +45,6 @@ void ObjectDistributor::init() | ||||
|  | ||||
| void ObjectDistributor::distributeLimitedObjects() | ||||
| { | ||||
| 	ObjectInfo oi; | ||||
| 	auto zones = map.getZones(); | ||||
|  | ||||
| 	for (auto primaryID : VLC->objtypeh->knownObjects()) | ||||
| @@ -81,6 +80,8 @@ void ObjectDistributor::distributeLimitedObjects() | ||||
| 					RandomGeneratorUtil::randomShuffle(matchingZones, zone.getRand()); | ||||
| 					for (auto& zone : matchingZones) | ||||
| 					{ | ||||
| 						ObjectInfo oi(primaryID, secondaryID); | ||||
| 						 | ||||
| 						oi.generateObject = [cb=map.mapInstance->cb, primaryID, secondaryID]() -> CGObjectInstance * | ||||
| 						{ | ||||
| 							return VLC->objtypeh->getHandlerFor(primaryID, secondaryID)->create(cb, nullptr); | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
|  | ||||
| #include "StdInc.h" | ||||
| #include "TreasurePlacer.h" | ||||
| #include "../CRmgTemplate.h" | ||||
| #include "../CMapGenerator.h" | ||||
| #include "../Functions.h" | ||||
| #include "ObjectManager.h" | ||||
| @@ -24,6 +25,7 @@ | ||||
| #include "../../mapObjectConstructors/AObjectTypeHandler.h" | ||||
| #include "../../mapObjectConstructors/CObjectClassesHandler.h" | ||||
| #include "../../mapObjectConstructors/DwellingInstanceConstructor.h" | ||||
| #include "../../rewardable/Info.h" | ||||
| #include "../../mapObjects/CGHeroInstance.h" | ||||
| #include "../../mapObjects/CGPandoraBox.h" | ||||
| #include "../../mapObjects/CQuest.h" | ||||
| @@ -37,15 +39,29 @@ | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| ObjectInfo::ObjectInfo(): | ||||
| 	destroyObject([](CGObjectInstance * obj){}) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::process() | ||||
| { | ||||
| 	if (zone.getMaxTreasureValue() == 0) | ||||
| 	{ | ||||
| 		//No treasures at all | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	tierValues = generator.getConfig().pandoraCreatureValues; | ||||
| 	// Add all native creatures | ||||
| 	for(auto const & cre : VLC->creh->objects) | ||||
| 	{ | ||||
| 		if(!cre->special && cre->getFaction() == zone.getTownType()) | ||||
| 		{ | ||||
| 			creatures.push_back(cre.get()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Get default objects | ||||
| 	addAllPossibleObjects(); | ||||
| 	// Override with custom objects | ||||
| 	objects.patchWithZoneConfig(zone, this); | ||||
|  | ||||
| 	auto * m = zone.getModificator<ObjectManager>(); | ||||
| 	if(m) | ||||
| 		createTreasures(*m); | ||||
| @@ -62,14 +78,37 @@ void TreasurePlacer::init() | ||||
|  | ||||
| void TreasurePlacer::addObjectToRandomPool(const ObjectInfo& oi) | ||||
| { | ||||
| 	if (oi.templates.empty()) | ||||
| 	{ | ||||
| 		logGlobal->error("Attempt to add ObjectInfo with no templates! Value: %d", oi.value); | ||||
| 		return; | ||||
| 	} | ||||
| 	if (!oi.generateObject) | ||||
| 	{ | ||||
| 		logGlobal->error("Attempt to add ObjectInfo with no generateObject function! Value: %d", oi.value); | ||||
| 		return; | ||||
| 	} | ||||
| 	if (!oi.maxPerZone) | ||||
| 	{ | ||||
| 		logGlobal->warn("Attempt to add ObjectInfo with 0 maxPerZone! Value: %d", oi.value); | ||||
| 		return; | ||||
| 	} | ||||
| 	RecursiveLock lock(externalAccessMutex); | ||||
| 	possibleObjects.push_back(oi); | ||||
| 	objects.addObject(oi); | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::addAllPossibleObjects() | ||||
| { | ||||
| 	ObjectInfo oi; | ||||
| 	 | ||||
| 	addCommonObjects(); | ||||
| 	addDwellings(); | ||||
| 	addPandoraBoxes(); | ||||
| 	addSeerHuts(); | ||||
| 	addPrisons(); | ||||
| 	addScrolls(); | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::addCommonObjects() | ||||
| { | ||||
| 	for(auto primaryID : VLC->objtypeh->knownObjects()) | ||||
| 	{ | ||||
| 		for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID)) | ||||
| @@ -83,21 +122,31 @@ void TreasurePlacer::addAllPossibleObjects() | ||||
| 					//Skip objects with per-map limit here | ||||
| 					continue; | ||||
| 				} | ||||
| 				ObjectInfo oi(primaryID, secondaryID); | ||||
| 				setBasicProperties(oi, CompoundMapObjectID(primaryID, secondaryID)); | ||||
|  | ||||
| 				oi.generateObject = [this, primaryID, secondaryID]() -> CGObjectInstance * | ||||
| 				{ | ||||
| 					return VLC->objtypeh->getHandlerFor(primaryID, secondaryID)->create(map.mapInstance->cb, nullptr); | ||||
| 				}; | ||||
| 				oi.value = rmgInfo.value; | ||||
| 				oi.probability = rmgInfo.rarity; | ||||
| 				oi.setTemplates(primaryID, secondaryID, zone.getTerrainType()); | ||||
| 				oi.maxPerZone = rmgInfo.zoneLimit; | ||||
|  | ||||
| 				if(!oi.templates.empty()) | ||||
| 					addObjectToRandomPool(oi); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::setBasicProperties(ObjectInfo & oi, CompoundMapObjectID objid) const | ||||
| { | ||||
| 	oi.generateObject = [this, objid]() -> CGObjectInstance * | ||||
| 	{ | ||||
| 		return VLC->objtypeh->getHandlerFor(objid)->create(map.mapInstance->cb, nullptr); | ||||
| 	}; | ||||
| 	oi.setTemplates(objid.primaryID, objid.secondaryID, zone.getTerrainType()); | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::addPrisons() | ||||
| { | ||||
| 	//Generate Prison on water only if it has a template | ||||
| 	auto prisonTemplates = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0)->getTemplates(zone.getTerrainType()); | ||||
| 	if (!prisonTemplates.empty()) | ||||
| @@ -119,7 +168,7 @@ void TreasurePlacer::addAllPossibleObjects() | ||||
| 		size_t prisonsLeft = getMaxPrisons(); | ||||
| 		for (int i = prisonsLevels - 1; i >= 0; i--) | ||||
| 		{ | ||||
| 			ObjectInfo oi; // Create new instance which will hold destructor operation | ||||
| 			ObjectInfo oi(Obj::PRISON, 0); // Create new instance which will hold destructor operation | ||||
|  | ||||
| 			oi.value = generator.getConfig().prisonValues[i]; | ||||
| 			if (oi.value > zone.getMaxTreasureValue()) | ||||
| @@ -157,22 +206,13 @@ void TreasurePlacer::addAllPossibleObjects() | ||||
| 				addObjectToRandomPool(oi); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::addDwellings() | ||||
| { | ||||
| 	if(zone.getType() == ETemplateZoneType::WATER) | ||||
| 		return; | ||||
| 	 | ||||
| 	//all following objects are unlimited | ||||
| 	oi.maxPerZone = std::numeric_limits<ui32>::max(); | ||||
|  | ||||
| 	std::vector<const CCreature *> creatures; //native creatures for this zone | ||||
| 	for(auto const & cre : VLC->creh->objects) | ||||
| 	{ | ||||
| 		if(!cre->special && cre->getFaction() == zone.getTownType()) | ||||
| 		{ | ||||
| 			creatures.push_back(cre.get()); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	//dwellings | ||||
| 	auto dwellingTypes = {Obj::CREATURE_GENERATOR1, Obj::CREATURE_GENERATOR4}; | ||||
| 	 | ||||
| @@ -199,6 +239,9 @@ void TreasurePlacer::addAllPossibleObjects() | ||||
| 			if(cre->getFaction() == zone.getTownType()) | ||||
| 			{ | ||||
| 				auto nativeZonesCount = static_cast<float>(map.getZoneCount(cre->getFaction())); | ||||
| 				ObjectInfo oi(dwellingType, secondaryID); | ||||
| 				setBasicProperties(oi, CompoundMapObjectID(dwellingType, secondaryID)); | ||||
|  | ||||
| 				oi.value = static_cast<ui32>(cre->getAIValue() * cre->getGrowth() * (1 + (nativeZonesCount / map.getTotalZoneCount()) + (nativeZonesCount / 2))); | ||||
| 				oi.probability = 40; | ||||
| 				 | ||||
| @@ -208,13 +251,20 @@ void TreasurePlacer::addAllPossibleObjects() | ||||
| 					obj->tempOwner = PlayerColor::NEUTRAL; | ||||
| 					return obj; | ||||
| 				}; | ||||
| 				oi.setTemplates(dwellingType, secondaryID, zone.getTerrainType()); | ||||
| 				if(!oi.templates.empty()) | ||||
| 					addObjectToRandomPool(oi); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::addScrolls() | ||||
| { | ||||
| 	if(zone.getType() == ETemplateZoneType::WATER) | ||||
| 		return; | ||||
|  | ||||
| 	ObjectInfo oi(Obj::SPELL_SCROLL, 0); | ||||
|  | ||||
| 	for(int i = 0; i < generator.getConfig().scrollValues.size(); i++) | ||||
| 	{ | ||||
| 		oi.generateObject = [i, this]() -> CGObjectInstance * | ||||
| @@ -239,7 +289,22 @@ void TreasurePlacer::addAllPossibleObjects() | ||||
| 			addObjectToRandomPool(oi); | ||||
| 	} | ||||
| 	 | ||||
| 	//pandora box with gold | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::addPandoraBoxes() | ||||
| { | ||||
| 	if(zone.getType() == ETemplateZoneType::WATER) | ||||
| 		return; | ||||
|  | ||||
| 	addPandoraBoxesWithGold(); | ||||
| 	addPandoraBoxesWithExperience(); | ||||
| 	addPandoraBoxesWithCreatures(); | ||||
| 	addPandoraBoxesWithSpells(); | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::addPandoraBoxesWithGold() | ||||
| { | ||||
| 	ObjectInfo oi(Obj::PANDORAS_BOX, 0); | ||||
| 	for(int i = 1; i < 5; i++) | ||||
| 	{ | ||||
| 		oi.generateObject = [this, i]() -> CGObjectInstance * | ||||
| @@ -260,8 +325,11 @@ void TreasurePlacer::addAllPossibleObjects() | ||||
| 		if(!oi.templates.empty()) | ||||
| 			addObjectToRandomPool(oi); | ||||
| 	} | ||||
| 	 | ||||
| 	//pandora box with experience | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::addPandoraBoxesWithExperience() | ||||
| { | ||||
| 	ObjectInfo oi(Obj::PANDORAS_BOX, 0); | ||||
| 	for(int i = 1; i < 5; i++) | ||||
| 	{ | ||||
| 		oi.generateObject = [this, i]() -> CGObjectInstance * | ||||
| @@ -282,49 +350,17 @@ void TreasurePlacer::addAllPossibleObjects() | ||||
| 		if(!oi.templates.empty()) | ||||
| 			addObjectToRandomPool(oi); | ||||
| 	} | ||||
| 	 | ||||
| 	//pandora box with creatures | ||||
| 	const std::vector<int> & tierValues = generator.getConfig().pandoraCreatureValues; | ||||
| 	 | ||||
| 	auto creatureToCount = [tierValues](const CCreature * creature) -> int | ||||
| 	{ | ||||
| 		if(!creature->getAIValue() || tierValues.empty()) //bug #2681 | ||||
| 			return 0; //this box won't be generated | ||||
| 		 | ||||
| 		//Follow the rules from https://heroes.thelazy.net/index.php/Pandora%27s_Box | ||||
|  | ||||
| 		int actualTier = creature->getLevel() > tierValues.size() ? | ||||
| 			tierValues.size() - 1 : | ||||
| 			creature->getLevel() - 1; | ||||
| 		float creaturesAmount = std::floor((static_cast<float>(tierValues[actualTier])) / creature->getAIValue()); | ||||
| 		if (creaturesAmount < 1) | ||||
| 		{ | ||||
| 			return 0; | ||||
| 		} | ||||
| 		else if(creaturesAmount <= 5) | ||||
| 		{ | ||||
| 			//No change | ||||
| 		} | ||||
| 		else if(creaturesAmount <= 12) | ||||
| 		{ | ||||
| 			creaturesAmount = std::ceil(creaturesAmount / 2) * 2; | ||||
| 		} | ||||
| 		else if(creaturesAmount <= 50) | ||||
| 		{ | ||||
| 			creaturesAmount = std::round(creaturesAmount / 5) * 5; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			creaturesAmount = std::round(creaturesAmount / 10) * 10; | ||||
| 		} | ||||
| 		return static_cast<int>(creaturesAmount); | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::addPandoraBoxesWithCreatures() | ||||
| { | ||||
| 	for(auto * creature : creatures) | ||||
| 	{ | ||||
| 		int creaturesAmount = creatureToCount(creature); | ||||
| 		if(!creaturesAmount) | ||||
| 			continue; | ||||
|  | ||||
| 		ObjectInfo oi(Obj::PANDORAS_BOX, 0); | ||||
| 		 | ||||
| 		oi.generateObject = [this, creature, creaturesAmount]() -> CGObjectInstance * | ||||
| 		{ | ||||
| @@ -339,12 +375,16 @@ void TreasurePlacer::addAllPossibleObjects() | ||||
| 			return obj; | ||||
| 		}; | ||||
| 		oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); | ||||
| 		oi.value = static_cast<ui32>((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())) / 3); | ||||
| 		oi.value = static_cast<ui32>(creature->getAIValue() * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())); | ||||
| 		oi.probability = 3; | ||||
| 		if(!oi.templates.empty()) | ||||
| 			addObjectToRandomPool(oi); | ||||
| 	} | ||||
| 	 | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::addPandoraBoxesWithSpells() | ||||
| { | ||||
| 	ObjectInfo oi(Obj::PANDORAS_BOX, 0); | ||||
| 	//Pandora with 12 spells of certain level | ||||
| 	for(int i = 1; i <= GameConstants::SPELL_LEVELS; i++) | ||||
| 	{ | ||||
| @@ -441,9 +481,14 @@ void TreasurePlacer::addAllPossibleObjects() | ||||
| 	oi.probability = 2; | ||||
| 	if(!oi.templates.empty()) | ||||
| 		addObjectToRandomPool(oi); | ||||
| 	 | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::addSeerHuts() | ||||
| { | ||||
| 	//Seer huts with creatures or generic rewards | ||||
|  | ||||
| 	ObjectInfo oi(Obj::SEER_HUT, 0); | ||||
|  | ||||
| 	if(zone.getConnectedZoneIds().size()) //Unlikely, but... | ||||
| 	{ | ||||
| 		auto * qap = zone.getModificator<QuestArtifactPlacer>(); | ||||
| @@ -588,12 +633,6 @@ void TreasurePlacer::addAllPossibleObjects() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| size_t TreasurePlacer::getPossibleObjectsSize() const | ||||
| { | ||||
| 	RecursiveLock lock(externalAccessMutex); | ||||
| 	return possibleObjects.size(); | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::setMaxPrisons(size_t count) | ||||
| { | ||||
| 	RecursiveLock lock(externalAccessMutex); | ||||
| @@ -606,6 +645,40 @@ size_t TreasurePlacer::getMaxPrisons() const | ||||
| 	return maxPrisons; | ||||
| } | ||||
|  | ||||
| int TreasurePlacer::creatureToCount(const CCreature * creature) const | ||||
| { | ||||
| 	if(!creature->getAIValue() || tierValues.empty()) //bug #2681 | ||||
| 		return 0; //this box won't be generated | ||||
| 	 | ||||
| 	//Follow the rules from https://heroes.thelazy.net/index.php/Pandora%27s_Box | ||||
|  | ||||
| 	int actualTier = creature->getLevel() > tierValues.size() ? | ||||
| 		tierValues.size() - 1 : | ||||
| 		creature->getLevel() - 1; | ||||
| 	float creaturesAmount = std::floor((static_cast<float>(tierValues[actualTier])) / creature->getAIValue()); | ||||
| 	if (creaturesAmount < 1) | ||||
| 	{ | ||||
| 		return 0; | ||||
| 	} | ||||
| 	else if(creaturesAmount <= 5) | ||||
| 	{ | ||||
| 		//No change | ||||
| 	} | ||||
| 	else if(creaturesAmount <= 12) | ||||
| 	{ | ||||
| 		creaturesAmount = std::ceil(creaturesAmount / 2) * 2; | ||||
| 	} | ||||
| 	else if(creaturesAmount <= 50) | ||||
| 	{ | ||||
| 		creaturesAmount = std::round(creaturesAmount / 5) * 5; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		creaturesAmount = std::round(creaturesAmount / 10) * 10; | ||||
| 	} | ||||
| 	return static_cast<int>(creaturesAmount); | ||||
| }; | ||||
|  | ||||
| bool TreasurePlacer::isGuardNeededForTreasure(int value) | ||||
| {// no guard in a zone with "monsters: none" and for small treasures; water zones cen get monster strength ZONE_NONE elsewhere if needed | ||||
| 	return zone.monsterStrength != EMonsterStrength::ZONE_NONE && value > minGuardedValue; | ||||
| @@ -623,6 +696,7 @@ std::vector<ObjectInfo*> TreasurePlacer::prepareTreasurePile(const CTreasureInfo | ||||
| 	bool hasLargeObject = false; | ||||
| 	while(currentValue <= static_cast<int>(desiredValue) - 100) //no objects with value below 100 are available | ||||
| 	{ | ||||
| 		// FIXME: Pointer might be invalidated after this | ||||
| 		auto * oi = getRandomObject(desiredValue, currentValue, !hasLargeObject); | ||||
| 		if(!oi) //fail | ||||
| 			break; | ||||
| @@ -674,12 +748,21 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*> | ||||
| 			accessibleArea.add(int3()); | ||||
| 		} | ||||
| 		 | ||||
| 		auto * object = oi->generateObject(); | ||||
| 		if(oi->templates.empty()) | ||||
| 		CGObjectInstance * object = nullptr; | ||||
| 		if (oi->generateObject) | ||||
| 		{ | ||||
| 			logGlobal->warn("Deleting randomized object with no templates: %s", object->getObjectName()); | ||||
| 			oi->destroyObject(object); | ||||
| 			delete object; | ||||
| 			object = oi->generateObject(); | ||||
| 			if(oi->templates.empty()) | ||||
| 			{ | ||||
| 				logGlobal->warn("Deleting randomized object with no templates: %s", object->getObjectName()); | ||||
| 				oi->destroyObject(object); | ||||
| 				delete object; | ||||
| 				continue; | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			logGlobal->error("ObjectInfo has no generateObject function! Templates: %d", oi->templates.size()); | ||||
| 			continue; | ||||
| 		} | ||||
| 		 | ||||
| @@ -785,7 +868,7 @@ ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValu | ||||
| 	ui32 maxVal = desiredValue - currentValue; | ||||
| 	ui32 minValue = static_cast<ui32>(0.25f * (desiredValue - currentValue)); | ||||
| 	 | ||||
| 	for(ObjectInfo & oi : possibleObjects) //copy constructor turned out to be costly | ||||
| 	for(ObjectInfo & oi : objects.getPossibleObjects()) //copy constructor turned out to be costly | ||||
| 	{ | ||||
| 		if(oi.value > maxVal) | ||||
| 			break; //this assumes values are sorted in ascending order | ||||
| @@ -859,24 +942,19 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) | ||||
| 	boost::sort(treasureInfo, valueComparator); | ||||
|  | ||||
| 	//sort treasures by ascending value so we can stop checking treasures with too high value | ||||
| 	boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool | ||||
| 	{ | ||||
| 		return oi1.value < oi2.value; | ||||
| 	}); | ||||
| 	objects.sortPossibleObjects(); | ||||
|  | ||||
| 	const size_t size = zone.area()->getTilesVector().size(); | ||||
|  | ||||
| 	int totalDensity = 0; | ||||
|  | ||||
| 	// FIXME: No need to use iterator here | ||||
| 	for (auto t  = treasureInfo.begin(); t != treasureInfo.end(); t++) | ||||
| 	{ | ||||
| 		std::vector<rmg::Object> treasures; | ||||
|  | ||||
| 		//discard objects with too high value to be ever placed | ||||
| 		vstd::erase_if(possibleObjects, [t](const ObjectInfo& oi) -> bool | ||||
| 		{ | ||||
| 			return oi.value > t->max; | ||||
| 		}); | ||||
| 		objects.discardObjectsAboveValue(t->max); | ||||
|  | ||||
| 		totalDensity += t->density; | ||||
|  | ||||
| @@ -895,7 +973,11 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo* oi) {return v + oi->value; }); | ||||
| 			int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, | ||||
| 				[](int v, const ObjectInfo* oi) | ||||
| 			{ | ||||
| 				return v + oi->value; | ||||
| 			}); | ||||
|  | ||||
| 			const ui32 maxPileGenerationAttempts = 2; | ||||
| 			for (ui32 attempt = 0; attempt < maxPileGenerationAttempts; attempt++) | ||||
| @@ -1016,13 +1098,222 @@ char TreasurePlacer::dump(const int3 & t) | ||||
| 	return Modificator::dump(t); | ||||
| } | ||||
|  | ||||
| void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrainType) | ||||
| void TreasurePlacer::ObjectPool::addObject(const ObjectInfo & info) | ||||
| { | ||||
| 	auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype); | ||||
| 	if(!templHandler) | ||||
| 		return; | ||||
| 	 | ||||
| 	templates = templHandler->getTemplates(terrainType); | ||||
| 	possibleObjects.push_back(info); | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::ObjectPool::updateObject(MapObjectID id, MapObjectSubID subid, ObjectInfo info) | ||||
| { | ||||
| 	/* | ||||
| 	Handle separately: | ||||
| 		- Dwellings | ||||
| 		- Prisons | ||||
| 		- Seer huts (quests) | ||||
| 		- Pandora Boxes | ||||
| 	*/ | ||||
| 	// FIXME: This will drop all templates | ||||
| 	customObjects.insert(std::make_pair(CompoundMapObjectID(id, subid), info)); | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone, TreasurePlacer * tp) | ||||
| { | ||||
| 	// FIXME: Wycina wszystkie obiekty poza pandorami i dwellami :? | ||||
|  | ||||
| 	// Copy standard objects if they are not already modified | ||||
| 	/* | ||||
| 	for (const auto & object : possibleObjects) | ||||
| 	{ | ||||
| 		for (const auto & templ : object.templates) | ||||
| 		{ | ||||
| 			// FIXME: Objects with same temmplates (Pandora boxes) are not added | ||||
| 			CompoundMapObjectID key(templ->id, templ->subid); | ||||
| 			if (!vstd::contains(customObjects, key)) | ||||
| 			{ | ||||
| 				customObjects[key] = object; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	*/ | ||||
| 	auto bannedObjectCategories = zone.getBannedObjectCategories(); | ||||
| 	auto categoriesSet = std::unordered_set<ObjectConfig::EObjectCategory>(bannedObjectCategories.begin(), bannedObjectCategories.end()); | ||||
|  | ||||
| 	if (categoriesSet.count(ObjectConfig::EObjectCategory::ALL)) | ||||
| 	{ | ||||
| 		possibleObjects.clear(); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		vstd::erase_if(possibleObjects, [this, &categoriesSet](const ObjectInfo & oi) -> bool | ||||
|  | ||||
| 		{ | ||||
| 			auto category = getObjectCategory(oi.getCompoundID()); | ||||
| 			if (categoriesSet.count(category)) | ||||
| 			{ | ||||
| 				logGlobal->info("Removing object %s from possible objects", oi.templates.front()->stringID); | ||||
| 				return true; | ||||
| 			} | ||||
| 			return false; | ||||
| 		}); | ||||
|  | ||||
| 		auto bannedObjects = zone.getBannedObjects(); | ||||
| 		auto bannedObjectsSet = std::set<CompoundMapObjectID>(bannedObjects.begin(), bannedObjects.end()); | ||||
| 		vstd::erase_if(possibleObjects, [&bannedObjectsSet](const ObjectInfo & object) | ||||
| 		{ | ||||
| 			for (const auto & templ : object.templates) | ||||
| 			{ | ||||
| 				CompoundMapObjectID key = object.getCompoundID(); | ||||
| 				if (bannedObjectsSet.count(key)) | ||||
| 				{ | ||||
| 					// FIXME: Stopped working, nothing is banned | ||||
| 					logGlobal->info("Banning object %s from possible objects", templ->stringID); | ||||
| 					return true; | ||||
| 				} | ||||
| 			} | ||||
| 			return false; | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	auto configuredObjects = zone.getConfiguredObjects(); | ||||
|  | ||||
| 	// FIXME: Access TreasurePlacer from ObjectPool | ||||
| 	for (auto & object : configuredObjects) | ||||
| 	{ | ||||
| 		tp->setBasicProperties(object, object.getCompoundID()); | ||||
| 		addObject(object); | ||||
| 		logGlobal->info("Added custom object of type %d.%d", object.primaryID, object.secondaryID); | ||||
| 	} | ||||
| 	// TODO: Overwrite or add to possibleObjects | ||||
|  | ||||
| 	// FIXME: Protect with mutex as well? | ||||
| 	/* | ||||
| 	for (const auto & customObject : customObjects) | ||||
| 	{ | ||||
| 		addObject(customObject.second); | ||||
| 	} | ||||
| 	*/ | ||||
| 	// TODO: Consider adding custom Pandora boxes with arbitrary content | ||||
| } | ||||
|  | ||||
| std::vector<ObjectInfo> & TreasurePlacer::ObjectPool::getPossibleObjects() | ||||
| { | ||||
| 	return possibleObjects; | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::ObjectPool::sortPossibleObjects() | ||||
| { | ||||
| 	boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool | ||||
| 	{ | ||||
| 		return oi1.value < oi2.value; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void TreasurePlacer::ObjectPool::discardObjectsAboveValue(ui32 value) | ||||
| { | ||||
| 	vstd::erase_if(possibleObjects, [value](const ObjectInfo& oi) -> bool | ||||
| 	{ | ||||
| 		return oi.value > value; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| ObjectConfig::EObjectCategory TreasurePlacer::ObjectPool::getObjectCategory(CompoundMapObjectID id) | ||||
| { | ||||
| 	auto name = VLC->objtypeh->getObjectHandlerName(id.primaryID); | ||||
|  | ||||
| 	if (name == "configurable") | ||||
| 	{ | ||||
| 		auto handler = VLC->objtypeh->getHandlerFor(id.primaryID, id.secondaryID); | ||||
| 		if (!handler) | ||||
| 		{ | ||||
| 			return ObjectConfig::EObjectCategory::NONE; | ||||
| 		} | ||||
|  | ||||
| 		auto temp = handler->getTemplates().front(); | ||||
| 		auto info = handler->getObjectInfo(temp); | ||||
|  | ||||
| 		if (info->hasGuards()) | ||||
| 		{ | ||||
| 			return ObjectConfig::EObjectCategory::CREATURE_BANK; | ||||
| 		} | ||||
| 		else if (info->givesResources()) | ||||
| 		{ | ||||
| 			return ObjectConfig::EObjectCategory::RESOURCE; | ||||
| 		} | ||||
| 		else if (info->givesArtifacts()) | ||||
| 		{ | ||||
| 			return ObjectConfig::EObjectCategory::RANDOM_ARTIFACT; | ||||
| 		} | ||||
| 		else if (info->givesBonuses()) | ||||
| 		{ | ||||
| 			return ObjectConfig::EObjectCategory::BONUS; | ||||
| 		} | ||||
|  | ||||
| 		return ObjectConfig::EObjectCategory::OTHER; | ||||
| 	} | ||||
| 	else if (name == "dwelling" || name == "randomDwelling") | ||||
| 	{ | ||||
| 		// TODO: Special handling for different tiers | ||||
| 		return ObjectConfig::EObjectCategory::DWELLING; | ||||
| 	} | ||||
| 	else if (name == "bank") | ||||
| 		return ObjectConfig::EObjectCategory::CREATURE_BANK; | ||||
| 	else if (name == "market") | ||||
| 		return ObjectConfig::EObjectCategory::OTHER; | ||||
| 	else if (name == "hillFort") | ||||
| 		return ObjectConfig::EObjectCategory::OTHER; | ||||
| 	else if (name == "resource" || name == "randomResource") | ||||
| 		return ObjectConfig::EObjectCategory::RESOURCE; | ||||
| 	else if (name == "randomArtifact") //"artifact" | ||||
| 		return ObjectConfig::EObjectCategory::RANDOM_ARTIFACT; | ||||
| 	else if (name == "artifact") | ||||
| 	{ | ||||
| 		if (id.primaryID == Obj::SPELL_SCROLL ) // randomArtifactTreasure | ||||
| 		{ | ||||
| 			return ObjectConfig::EObjectCategory::SPELL_SCROLL; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			return ObjectConfig::EObjectCategory::QUEST_ARTIFACT; | ||||
| 		} | ||||
| 	} | ||||
| 	else if (name == "denOfThieves") | ||||
| 		return ObjectConfig::EObjectCategory::OTHER; | ||||
| 	else if (name == "lighthouse") | ||||
| 	{ | ||||
| 		return ObjectConfig::EObjectCategory::BONUS; | ||||
| 	} | ||||
| 	else if (name == "magi") | ||||
| 	{ | ||||
| 		// TODO: By default, both eye and hut are banned in every zone | ||||
| 		return ObjectConfig::EObjectCategory::OTHER; | ||||
| 	} | ||||
| 	else if (name == "mine") | ||||
| 		return ObjectConfig::EObjectCategory::RESOURCE_GENERATOR; | ||||
| 	else if (name == "pandora") | ||||
| 		return ObjectConfig::EObjectCategory::PANDORAS_BOX; | ||||
| 	else if (name == "prison") | ||||
| 	{ | ||||
| 		// TODO: Prisons should be configurable | ||||
| 		return ObjectConfig::EObjectCategory::OTHER; | ||||
| 	} | ||||
| 	else if (name == "questArtifact") | ||||
| 	{ | ||||
| 		// TODO: There are no dedicated quest artifacts, needs extra logic | ||||
| 		return ObjectConfig::EObjectCategory::QUEST_ARTIFACT; | ||||
| 	} | ||||
| 	else if (name == "seerHut") | ||||
| 	{ | ||||
| 		return ObjectConfig::EObjectCategory::SEER_HUT; | ||||
| 	} | ||||
| 	else if (name == "siren") | ||||
| 		return ObjectConfig::EObjectCategory::BONUS; | ||||
| 	else if (name == "obelisk") | ||||
| 		return ObjectConfig::EObjectCategory::OTHER; | ||||
|  | ||||
| 	// TODO: ObjectConfig::EObjectCategory::SPELL_SCROLL | ||||
|  | ||||
| 	// Not interesting for us | ||||
| 	return ObjectConfig::EObjectCategory::NONE; | ||||
| } | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|   | ||||
| @@ -9,6 +9,8 @@ | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "../ObjectInfo.h" | ||||
| #include "../Zone.h" | ||||
| #include "../../mapObjects/ObjectTemplate.h" | ||||
|  | ||||
| @@ -18,21 +20,7 @@ class CGObjectInstance; | ||||
| class ObjectManager; | ||||
| class RmgMap; | ||||
| class CMapGenerator; | ||||
|  | ||||
| struct ObjectInfo | ||||
| { | ||||
| 	ObjectInfo(); | ||||
|  | ||||
| 	std::vector<std::shared_ptr<const ObjectTemplate>> templates; | ||||
| 	ui32 value = 0; | ||||
| 	ui16 probability = 0; | ||||
| 	ui32 maxPerZone = 1; | ||||
| 	//ui32 maxPerMap; //unused | ||||
| 	std::function<CGObjectInstance *()> generateObject; | ||||
| 	std::function<void(CGObjectInstance *)> destroyObject; | ||||
| 	 | ||||
| 	void setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrain); | ||||
| }; | ||||
| class ObjectConfig; | ||||
|  | ||||
| class TreasurePlacer: public Modificator | ||||
| { | ||||
| @@ -45,11 +33,27 @@ public: | ||||
| 	 | ||||
| 	void createTreasures(ObjectManager & manager); | ||||
| 	void addObjectToRandomPool(const ObjectInfo& oi); | ||||
| 	void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects | ||||
| 	void setBasicProperties(ObjectInfo & oi, CompoundMapObjectID objid) const; | ||||
|  | ||||
| 	// TODO: Can be defaulted to addAllPossibleObjects, but then each object will need to be configured | ||||
| 	void addCommonObjects(); | ||||
| 	void addDwellings(); | ||||
| 	void addPandoraBoxes(); | ||||
| 	void addPandoraBoxesWithGold(); | ||||
| 	void addPandoraBoxesWithExperience(); | ||||
| 	void addPandoraBoxesWithCreatures(); | ||||
| 	void addPandoraBoxesWithSpells(); | ||||
| 	void addSeerHuts(); | ||||
| 	void addPrisons(); | ||||
| 	void addScrolls(); | ||||
| 	void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects | ||||
| 	// TODO: Read custom object config from zone file | ||||
|  | ||||
| 	/// Get all objects for this terrain | ||||
|  | ||||
| 	size_t getPossibleObjectsSize() const; | ||||
| 	void setMaxPrisons(size_t count); | ||||
| 	size_t getMaxPrisons() const; | ||||
| 	int creatureToCount(const CCreature * creature) const; | ||||
| 	 | ||||
| protected: | ||||
| 	bool isGuardNeededForTreasure(int value); | ||||
| @@ -59,7 +63,26 @@ protected: | ||||
| 	rmg::Object constructTreasurePile(const std::vector<ObjectInfo*> & treasureInfos, bool densePlacement = false); | ||||
|  | ||||
| protected: | ||||
| 	std::vector<ObjectInfo> possibleObjects; | ||||
| 	class ObjectPool | ||||
| 	{ | ||||
| 	public: | ||||
| 		void addObject(const ObjectInfo & info); | ||||
| 		void updateObject(MapObjectID id, MapObjectSubID subid, ObjectInfo info); | ||||
| 		std::vector<ObjectInfo> & getPossibleObjects(); | ||||
| 		void patchWithZoneConfig(const Zone & zone, TreasurePlacer * tp); | ||||
| 		void sortPossibleObjects(); | ||||
| 		void discardObjectsAboveValue(ui32 value); | ||||
|  | ||||
| 		ObjectConfig::EObjectCategory getObjectCategory(CompoundMapObjectID id); | ||||
|  | ||||
| 	private: | ||||
|  | ||||
| 		std::vector<ObjectInfo> possibleObjects; | ||||
| 		std::map<CompoundMapObjectID, ObjectInfo> customObjects; | ||||
|  | ||||
| 	} objects; | ||||
| 	// TODO: Need to nagivate and update these | ||||
|  | ||||
| 	int minGuardedValue = 0; | ||||
| 	 | ||||
| 	rmg::Area treasureArea; | ||||
| @@ -67,6 +90,9 @@ protected: | ||||
| 	rmg::Area guards; | ||||
|  | ||||
| 	size_t maxPrisons; | ||||
|  | ||||
| 	std::vector<const CCreature *> creatures; //native creatures for this zone | ||||
| 	std::vector<int> tierValues; | ||||
| }; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|   | ||||
		Reference in New Issue
	
	Block a user