mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Rmg water support (#751)
* Roads added to shipyard * Load general rmg parameters from config * Fix issue with default zone guard * Move magic numbers related to balance to randomMap.json
This commit is contained in:
		
				
					committed by
					
						 Andrii Danylchenko
						Andrii Danylchenko
					
				
			
			
				
	
			
			
			
						parent
						
							5c1a66ab69
						
					
				
				
					commit
					5054ee011a
				
			
							
								
								
									
										59
									
								
								config/randomMap.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								config/randomMap.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| { | ||||
|   "terrain" : | ||||
|   { | ||||
|     "undergroundAllow" : ["lava"], //others to be replaced by subterranena | ||||
|     "groundProhibit" : ["subterranean"] //to be replaced by dirt | ||||
|   }, | ||||
|   "waterZone" : | ||||
|   { | ||||
|     "treasure" : | ||||
|     [ | ||||
|         { "min" : 2000, "max" : 6000, "density" : 1 }, | ||||
|         { "min" : 100, "max" : 1000, "density" : 5 } | ||||
|     ], | ||||
|     "shipyard" : | ||||
|     { | ||||
|       "value" : 3500 | ||||
|     } | ||||
|   }, | ||||
|   "mines" : | ||||
|   { | ||||
|     "value" : | ||||
|     { | ||||
|       "wood" : 1500, | ||||
|       "ore" : 1500, | ||||
|       "gems" : 3500, | ||||
|       "crystal" : 3500, | ||||
|       "mercury" : 3500, | ||||
|       "sulfur" : 3500, | ||||
|       "gold" : 7000 | ||||
|     }, | ||||
|     "extraResourcesLimit" : 3 | ||||
|   }, | ||||
|   "minGuardStrength" : 2000, | ||||
|   "defaultRoadType" : "cobblestone_road", | ||||
|   "treasureValueLimit" : 20000, //generate pandora with gold for treasure above this limit | ||||
|   "prisons" : | ||||
|   { | ||||
|     "experience" : [0, 5000, 15000, 90000, 500000], | ||||
|     "value" : [2500, 5000, 10000, 20000, 30000], | ||||
|   }, | ||||
|   "scrolls" : | ||||
|   { | ||||
|     "value" : [500, 2000, 3000, 4000, 5000], | ||||
|   }, | ||||
|   "pandoras" : | ||||
|   { | ||||
|     "valueMultiplierGold" : 5000, | ||||
|     "valueMultiplierExperience" : 6000, | ||||
|     "valueMultiplierSpells" : 2500, | ||||
|     "valueSpellSchool" : 15000, | ||||
|     "valueSpell60" : 30000, | ||||
|     "creaturesValue" : [5000, 7000, 9000, 12000, 16000, 21000, 27000] | ||||
|   }, | ||||
|   "quests" :  | ||||
|   { | ||||
|     "value" : [2000, 5333, 8666, 12000], | ||||
|     "rewardValue" : [5000, 10000, 15000, 20000] | ||||
|   } | ||||
| } | ||||
| @@ -60,6 +60,12 @@ | ||||
| 					"minimum" : 0 | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"waterContent": | ||||
| 		{ | ||||
| 			"enum": ["none", "normal", "islands"], | ||||
| 			"additionalProperties" : false, | ||||
| 			"type": "string" | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| @@ -73,6 +79,10 @@ | ||||
| 			"type": "array", | ||||
| 			"items":{"$ref" : "#/definitions/connection"}	 | ||||
| 		}, | ||||
| 		"allowedWaterContent": { | ||||
| 			"type": "array", | ||||
| 			"items": {"$ref" : "#/definitions/waterContent"} | ||||
| 		}, | ||||
| 		"required" : ["zones", "connections"], | ||||
| 		"additionalProperties" : false | ||||
| 	} | ||||
|   | ||||
| @@ -246,7 +246,7 @@ void CMapGenOptions::finalize(CRandomGenerator & rand) | ||||
|  | ||||
| 	if(waterContent == EWaterContent::RANDOM) | ||||
| 	{ | ||||
| 		waterContent = static_cast<EWaterContent::EWaterContent>(rand.nextInt(EWaterContent::NONE, EWaterContent::ISLANDS)); | ||||
| 		waterContent = *RandomGeneratorUtil::nextItem(mapTemplate->getWaterContentAllowed(), rand); | ||||
| 	} | ||||
| 	if(monsterStrength == EMonsterStrength::RANDOM) | ||||
| 	{ | ||||
|   | ||||
| @@ -62,10 +62,89 @@ CMapGenerator::CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed) : | ||||
| 	zonesTotal(0), tiles(nullptr), prisonsRemaining(0), | ||||
|     monolithIndex(0) | ||||
| { | ||||
| 	loadConfig(); | ||||
| 	rand.setSeed(this->randomSeed); | ||||
| 	mapGenOptions.finalize(rand); | ||||
| } | ||||
|  | ||||
| void CMapGenerator::loadConfig() | ||||
| { | ||||
| 	static std::map<std::string, ETerrainType> terrainMap | ||||
| 	{ | ||||
| 		{"dirt", ETerrainType::DIRT}, | ||||
| 		{"sand", ETerrainType::SAND}, | ||||
| 		{"grass", ETerrainType::GRASS}, | ||||
| 		{"snow", ETerrainType::SNOW}, | ||||
| 		{"swamp", ETerrainType::SWAMP}, | ||||
| 		{"subterranean", ETerrainType::SUBTERRANEAN}, | ||||
| 		{"lava", ETerrainType::LAVA}, | ||||
| 		{"rough", ETerrainType::ROUGH} | ||||
| 	}; | ||||
| 	static const std::map<std::string, Res::ERes> resMap | ||||
| 	{ | ||||
| 		{"wood", Res::ERes::WOOD}, | ||||
| 		{"ore", Res::ERes::ORE}, | ||||
| 		{"gems", Res::ERes::GEMS}, | ||||
| 		{"crystal", Res::ERes::CRYSTAL}, | ||||
| 		{"mercury", Res::ERes::MERCURY}, | ||||
| 		{"sulfur", Res::ERes::SULFUR}, | ||||
| 		{"gold", Res::ERes::GOLD}, | ||||
| 	}; | ||||
| 	static std::map<std::string, ERoadType::ERoadType> roadTypeMap | ||||
| 	{ | ||||
| 		{"dirt_road", ERoadType::DIRT_ROAD}, | ||||
| 		{"gravel_road", ERoadType::GRAVEL_ROAD}, | ||||
| 		{"cobblestone_road", ERoadType::COBBLESTONE_ROAD} | ||||
| 	}; | ||||
| 	static const ResourceID path("config/randomMap.json"); | ||||
| 	JsonNode randomMapJson(path); | ||||
| 	for(auto& s : randomMapJson["terrain"]["undergroundAllow"].Vector()) | ||||
| 	{ | ||||
| 		if(!s.isNull()) | ||||
| 			config.terrainUndergroundAllowed.push_back(terrainMap[s.String()]); | ||||
| 	} | ||||
| 	for(auto& s : randomMapJson["terrain"]["groundProhibit"].Vector()) | ||||
| 	{ | ||||
| 		if(!s.isNull()) | ||||
| 			config.terrainGroundProhibit.push_back(terrainMap[s.String()]); | ||||
| 	} | ||||
| 	config.shipyardGuard = randomMapJson["waterZone"]["shipyard"]["value"].Integer(); | ||||
| 	for(auto & treasure : randomMapJson["waterZone"]["treasure"].Vector()) | ||||
| 	{ | ||||
| 		config.waterTreasure.emplace_back(treasure["min"].Integer(), treasure["max"].Integer(), treasure["density"].Integer()); | ||||
| 	} | ||||
| 	for(auto& s : resMap) | ||||
| 	{ | ||||
| 		config.mineValues[s.second] = randomMapJson["mines"]["value"][s.first].Integer(); | ||||
| 	} | ||||
| 	config.mineExtraResources = randomMapJson["mines"]["extraResourcesLimit"].Integer(); | ||||
| 	config.minGuardStrength = randomMapJson["minGuardStrength"].Integer(); | ||||
| 	config.defaultRoadType = roadTypeMap[randomMapJson["defaultRoadType"].String()]; | ||||
| 	config.treasureValueLimit = randomMapJson["treasureValueLimit"].Integer(); | ||||
| 	for(auto & i : randomMapJson["prisons"]["experience"].Vector()) | ||||
| 		config.prisonExperience.push_back(i.Integer()); | ||||
| 	for(auto & i : randomMapJson["prisons"]["value"].Vector()) | ||||
| 		config.prisonValues.push_back(i.Integer()); | ||||
| 	for(auto & i : randomMapJson["scrolls"]["value"].Vector()) | ||||
| 		config.scrollValues.push_back(i.Integer()); | ||||
| 	for(auto & i : randomMapJson["pandoras"]["creaturesValue"].Vector()) | ||||
| 		config.pandoraCreatureValues.push_back(i.Integer()); | ||||
| 	for(auto & i : randomMapJson["quests"]["value"].Vector()) | ||||
| 		config.questValues.push_back(i.Integer()); | ||||
| 	for(auto & i : randomMapJson["quests"]["rewardValue"].Vector()) | ||||
| 		config.questRewardValues.push_back(i.Integer()); | ||||
| 	config.pandoraMultiplierGold = randomMapJson["pandoras"]["valueMultiplierGold"].Integer(); | ||||
| 	config.pandoraMultiplierExperience = randomMapJson["pandoras"]["valueMultiplierExperience"].Integer(); | ||||
| 	config.pandoraMultiplierSpells = randomMapJson["pandoras"]["valueMultiplierSpells"].Integer(); | ||||
| 	config.pandoraSpellSchool = randomMapJson["pandoras"]["valueSpellSchool"].Integer(); | ||||
| 	config.pandoraSpell60 = randomMapJson["pandoras"]["valueSpell60"].Integer(); | ||||
| } | ||||
|  | ||||
| const CMapGenerator::Config & CMapGenerator::getConfig() const | ||||
| { | ||||
| 	return config; | ||||
| } | ||||
|  | ||||
| void CMapGenerator::initTiles() | ||||
| { | ||||
| 	map->initTerrain(); | ||||
| @@ -309,8 +388,10 @@ void CMapGenerator::genZones() | ||||
| void CMapGenerator::createWaterTreasures() | ||||
| { | ||||
| 	//add treasures on water | ||||
| 	getZoneWater().second->addTreasureInfo(CTreasureInfo{100, 1000, 5}); | ||||
| 	getZoneWater().second->addTreasureInfo(CTreasureInfo{2000, 6000, 1}); | ||||
| 	for(auto & treasureInfo : getConfig().waterTreasure) | ||||
| 	{ | ||||
| 		getZoneWater().second->addTreasureInfo(treasureInfo); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CMapGenerator::prepareWaterTiles() | ||||
|   | ||||
| @@ -52,11 +52,31 @@ public: | ||||
| class DLL_LINKAGE CMapGenerator | ||||
| { | ||||
| public: | ||||
| 	struct Config | ||||
| 	{ | ||||
| 		std::vector<ETerrainType> terrainUndergroundAllowed; | ||||
| 		std::vector<ETerrainType> terrainGroundProhibit; | ||||
| 		std::vector<CTreasureInfo> waterTreasure; | ||||
| 		int shipyardGuard; | ||||
| 		int mineExtraResources; | ||||
| 		std::map<Res::ERes, int> mineValues; | ||||
| 		int minGuardStrength; | ||||
| 		ERoadType::ERoadType defaultRoadType; | ||||
| 		int treasureValueLimit; | ||||
| 		std::vector<int> prisonExperience, prisonValues; | ||||
| 		std::vector<int> scrollValues; | ||||
| 		int pandoraMultiplierGold, pandoraMultiplierExperience, pandoraMultiplierSpells, pandoraSpellSchool, pandoraSpell60; | ||||
| 		std::vector<int> pandoraCreatureValues; | ||||
| 		std::vector<int> questValues, questRewardValues; | ||||
| 	}; | ||||
| 	 | ||||
| 	using Zones = std::map<TRmgTemplateZoneId, std::shared_ptr<CRmgTemplateZone>>; | ||||
|  | ||||
| 	explicit CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed = std::time(nullptr)); | ||||
| 	~CMapGenerator(); // required due to std::unique_ptr | ||||
| 	 | ||||
| 	const Config & getConfig() const; | ||||
| 	 | ||||
| 	mutable std::unique_ptr<CMap> map; | ||||
| 	CRandomGenerator rand; | ||||
| 	 | ||||
| @@ -111,6 +131,7 @@ public: | ||||
| private: | ||||
| 	int randomSeed; | ||||
| 	CMapGenOptions& mapGenOptions; | ||||
| 	Config config; | ||||
| 	 | ||||
| 	std::vector<rmg::ZoneConnection> connectionsLeft; | ||||
| 	Zones zones; | ||||
| @@ -129,6 +150,8 @@ private: | ||||
| 	void checkIsOnMap(const int3 &tile) const; //throws | ||||
|  | ||||
| 	/// Generation methods | ||||
| 	void loadConfig(); | ||||
| 	 | ||||
| 	std::string getMapDescription() const; | ||||
|  | ||||
| 	void initPrisonsRemaining(); | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
|  | ||||
| #include "StdInc.h" | ||||
| #include <vstd/ContainerUtils.h> | ||||
| #include <boost/bimap.hpp> | ||||
| #include "CRmgTemplate.h" | ||||
|  | ||||
| #include "../mapping/CMap.h" | ||||
| @@ -359,7 +360,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) | ||||
| 			rawStrength = static_cast<decltype(rawStrength)>(zoneMonsterStrength); | ||||
| 			rawStrength++; | ||||
| 		} | ||||
| 		handler.serializeEnum("monsters", rawStrength, STRENGTH); | ||||
| 		handler.serializeEnum("monsters", rawStrength, EMonsterStrength::ZONE_NORMAL + 1, STRENGTH); | ||||
| 		if(!handler.saving) | ||||
| 		{ | ||||
| 			rawStrength--; | ||||
| @@ -443,6 +444,11 @@ bool CRmgTemplate::isWaterContentAllowed(EWaterContent::EWaterContent waterConte | ||||
| 	return waterContent == EWaterContent::EWaterContent::RANDOM || allowedWaterContent.count(waterContent); | ||||
| } | ||||
|  | ||||
| const std::set<EWaterContent::EWaterContent> & CRmgTemplate::getWaterContentAllowed() const | ||||
| { | ||||
| 	return allowedWaterContent; | ||||
| } | ||||
|  | ||||
| void CRmgTemplate::setId(const std::string & value) | ||||
| { | ||||
| 	id = value; | ||||
| @@ -583,6 +589,32 @@ void CRmgTemplate::serializeJson(JsonSerializeFormat & handler) | ||||
| 		auto connectionsData = handler.enterArray("connections"); | ||||
| 		connectionsData.serializeStruct(connections); | ||||
| 	} | ||||
| 	 | ||||
| 	{ | ||||
| 		boost::bimap<EWaterContent::EWaterContent, std::string> enc; | ||||
| 		enc.insert({EWaterContent::NONE, "none"}); | ||||
| 		enc.insert({EWaterContent::NORMAL, "normal"}); | ||||
| 		enc.insert({EWaterContent::ISLANDS, "islands"}); | ||||
| 		JsonNode node; | ||||
| 		if(handler.saving) | ||||
| 		{ | ||||
| 			node.setType(JsonNode::JsonType::DATA_VECTOR); | ||||
| 			for(auto wc : allowedWaterContent) | ||||
| 			{ | ||||
| 				JsonNode n; | ||||
| 				n.String() = enc.left.at(wc); | ||||
| 				node.Vector().push_back(n); | ||||
| 			} | ||||
| 		} | ||||
| 		handler.serializeRaw("allowedWaterContent", node, boost::none); | ||||
| 		if(!handler.saving) | ||||
| 		{ | ||||
| 			for(auto wc : node.Vector()) | ||||
| 			{ | ||||
| 				allowedWaterContent.insert(enc.right.at(std::string(wc.String()))); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	{ | ||||
| 		auto zonesData = handler.enterStruct("zones"); | ||||
|   | ||||
| @@ -181,6 +181,7 @@ public: | ||||
|  | ||||
| 	bool matchesSize(const int3 & value) const; | ||||
| 	bool isWaterContentAllowed(EWaterContent::EWaterContent waterContent) const; | ||||
| 	const std::set<EWaterContent::EWaterContent> & getWaterContentAllowed() const; | ||||
|  | ||||
| 	void setId(const std::string & value); | ||||
| 	const std::string & getName() const; | ||||
|   | ||||
| @@ -641,7 +641,7 @@ void CRmgTemplateZone::waterConnection(CRmgTemplateZone& dst) | ||||
| 			 | ||||
| 			if(dst.getType() == ETemplateZoneType::PLAYER_START || dst.getType() == ETemplateZoneType::CPU_START || zoneTowns) | ||||
| 			{ | ||||
| 				coastTile = dst.createShipyard(lake.tiles, 3500); | ||||
| 				coastTile = dst.createShipyard(lake.tiles, gen->getConfig().shipyardGuard); | ||||
| 				if(!coastTile.valid()) | ||||
| 				{ | ||||
| 					coastTile = makeBoat(dst.getId(), lake.tiles); | ||||
| @@ -1266,7 +1266,7 @@ bool CRmgTemplateZone::addMonster(int3 &pos, si32 strength, bool clearSurroundin | ||||
| 	int strength2 = static_cast<int>(std::max(0.f, (strength - value2[monsterStrength]) * multiplier2[monsterStrength])); | ||||
|  | ||||
| 	strength = strength1 + strength2; | ||||
| 	if (strength < 2000) | ||||
| 	if (strength < gen->getConfig().minGuardStrength) | ||||
| 		return false; //no guard at all | ||||
|  | ||||
| 	CreatureID creId = CreatureID::NONE; | ||||
| @@ -1742,14 +1742,14 @@ void CRmgTemplateZone::initTerrainType () | ||||
|  | ||||
| 		//TODO: allow new types of terrain? | ||||
| 		{ | ||||
| 			if (isUnderground()) | ||||
| 			if(isUnderground()) | ||||
| 			{ | ||||
| 				if (terrainType != ETerrainType::LAVA) | ||||
| 				if(!vstd::contains(gen->getConfig().terrainUndergroundAllowed, terrainType)) | ||||
| 					terrainType = ETerrainType::SUBTERRANEAN; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				if (terrainType == ETerrainType::SUBTERRANEAN) | ||||
| 				if(vstd::contains(gen->getConfig().terrainGroundProhibit, terrainType)) | ||||
| 					terrainType = ETerrainType::DIRT; | ||||
| 			} | ||||
| 		} | ||||
| @@ -1767,8 +1767,6 @@ void CRmgTemplateZone::paintZoneTerrain (ETerrainType terrainType) | ||||
| bool CRmgTemplateZone::placeMines () | ||||
| { | ||||
| 	using namespace Res; | ||||
| 	static const std::map<ERes, int> mineValue{{ERes::WOOD, 1500}, {ERes::ORE, 1500}, {ERes::GEMS, 3500}, {ERes::CRYSTAL, 3500}, {ERes::MERCURY, 3500}, {ERes::SULFUR, 3500}, {ERes::GOLD, 7000}}; | ||||
| 	 | ||||
| 	std::vector<CGMine*> createdMines; | ||||
| 	 | ||||
| 	for(const auto & mineInfo : mines) | ||||
| @@ -1783,22 +1781,26 @@ bool CRmgTemplateZone::placeMines () | ||||
| 			createdMines.push_back(mine); | ||||
| 			 | ||||
| 			if(!i && (res == ERes::WOOD || res == ERes::ORE)) | ||||
| 				addCloseObject(mine, mineValue.at(res)); //only first woor&ore mines are close | ||||
| 				addCloseObject(mine, gen->getConfig().mineValues.at(res)); //only first wood&ore mines are close | ||||
| 			else | ||||
| 				addRequiredObject(mine, mineValue.at(res)); | ||||
| 				addRequiredObject(mine, gen->getConfig().mineValues.at(res)); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	//create extra resources | ||||
| 	for(auto * mine : createdMines) | ||||
| 	if(int extraRes = gen->getConfig().mineExtraResources) | ||||
| 	{ | ||||
| 		for(int rc = gen->rand.nextInt(1, 3); rc > 0; --rc) | ||||
| 		for(auto * mine : createdMines) | ||||
| 		{ | ||||
| 			auto resourse = (CGResource*) VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create(ObjectTemplate()); | ||||
| 			resourse->amount = CGResource::RANDOM_AMOUNT; | ||||
| 			addNearbyObject(resourse, mine); | ||||
| 			for(int rc = gen->rand.nextInt(1, extraRes); rc > 0; --rc) | ||||
| 			{ | ||||
| 				auto resourse = (CGResource*) VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create(ObjectTemplate()); | ||||
| 				resourse->amount = CGResource::RANDOM_AMOUNT; | ||||
| 				addNearbyObject(resourse, mine); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 		 | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
| @@ -2072,69 +2074,62 @@ bool CRmgTemplateZone::createShipyard(const int3 & position, si32 guardStrength) | ||||
| 	shipyard->tempOwner = PlayerColor::NEUTRAL; | ||||
| 	 | ||||
| 	setTemplateForObject(shipyard); | ||||
| 	std::vector<int3> offsets; | ||||
| 	std::vector<int3> outOffsets; | ||||
| 	auto tilesBlockedByObject = shipyard->getBlockedOffsets(); | ||||
| 	tilesBlockedByObject.insert(shipyard->getVisitableOffset()); | ||||
| 	shipyard->getOutOffsets(offsets); | ||||
| 	shipyard->getOutOffsets(outOffsets); | ||||
| 	 | ||||
| 	int3 targetTile(-1, -1, -1); | ||||
| 	std::set<int3> shipAccessCandidates; | ||||
| 	 | ||||
| 	for(auto& candidateTile : possibleTiles) | ||||
| 	for(const auto & outOffset : outOffsets) | ||||
| 	{ | ||||
| 		bool foundTargetPosition = false; | ||||
| 		for(const auto & offset : offsets) | ||||
| 		auto candidateTile = position - outOffset; | ||||
| 		std::set<int3> tilesBlockedAbsolute; | ||||
| 		 | ||||
| 		//check space under object | ||||
| 		bool allClear = true; | ||||
| 		for(const auto & objectTileOffset : tilesBlockedByObject) | ||||
| 		{ | ||||
| 			if(candidateTile+offset == position) | ||||
| 			auto objectTile = candidateTile + objectTileOffset; | ||||
| 			tilesBlockedAbsolute.insert(objectTile); | ||||
| 			if(!gen->map->isInTheMap(objectTile) || !gen->isPossible(objectTile) || gen->getZoneID(objectTile)!=id) | ||||
| 			{ | ||||
| 				std::set<int3> tilesBlockedAbsolute; | ||||
| 				//check space under object | ||||
| 				bool allClear = true; | ||||
| 				for(const auto & objectTileOffset : tilesBlockedByObject) | ||||
| 				{ | ||||
| 					auto objectTile = candidateTile + objectTileOffset; | ||||
| 					tilesBlockedAbsolute.insert(objectTile); | ||||
| 					if(!gen->map->isInTheMap(objectTile) || !gen->isPossible(objectTile) || gen->getZoneID(objectTile)!=id) | ||||
| 					{ | ||||
| 						allClear = false; | ||||
| 						break; | ||||
| 					} | ||||
| 				} | ||||
| 				if(!allClear) //cannot place shipyard anyway | ||||
| 					break; | ||||
| 				 | ||||
| 				//prepare temporary map | ||||
| 				for(auto& blockedPos : tilesBlockedAbsolute) | ||||
| 					gen->setOccupied(blockedPos, ETileType::USED); | ||||
| 				 | ||||
| 				//check if position is accessible | ||||
| 				gen->foreach_neighbour(position, [this, &shipAccessCandidates](const int3 & v) | ||||
| 				{ | ||||
| 					if(!gen->isBlocked(v) && gen->getZoneID(v)==id) | ||||
| 					{ | ||||
| 						//make sure that it's possible to create path to boarding position | ||||
| 						if(crunchPath(v, findClosestTile(freePaths, v), false, nullptr)) | ||||
| 							shipAccessCandidates.insert(v); | ||||
| 					} | ||||
| 				}); | ||||
| 				 | ||||
| 				//rollback temporary map | ||||
| 				for(auto& blockedPos : tilesBlockedAbsolute) | ||||
| 					gen->setOccupied(blockedPos, ETileType::POSSIBLE); | ||||
| 				 | ||||
| 				if(!shipAccessCandidates.empty()) | ||||
| 				{ | ||||
| 					foundTargetPosition = true; | ||||
| 				} | ||||
| 				 | ||||
| 				break; //no need to check other offsets as we already found position | ||||
| 				allClear = false; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		if(!allClear) //cannot place shipyard anyway | ||||
| 			continue; | ||||
| 		 | ||||
| 		if(foundTargetPosition && isAccessibleFromSomewhere(shipyard->appearance, candidateTile)) | ||||
| 		//prepare temporary map | ||||
| 		for(auto& blockedPos : tilesBlockedAbsolute) | ||||
| 			gen->setOccupied(blockedPos, ETileType::USED); | ||||
| 		 | ||||
| 		 | ||||
| 		//check if boarding position is accessible | ||||
| 		gen->foreach_neighbour(position, [this, &shipAccessCandidates](const int3 & v) | ||||
| 		{ | ||||
| 			if(!gen->isBlocked(v) && gen->getZoneID(v)==id) | ||||
| 			{ | ||||
| 				//make sure that it's possible to create path to boarding position | ||||
| 				if(connectWithCenter(v, false, false)) | ||||
| 					shipAccessCandidates.insert(v); | ||||
| 			} | ||||
| 		}); | ||||
| 		 | ||||
| 		//check if we can connect shipyard entrance with path | ||||
| 		if(!connectWithCenter(candidateTile + shipyard->getVisitableOffset(), false)) | ||||
| 			shipAccessCandidates.clear(); | ||||
| 				 | ||||
| 		//rollback temporary map | ||||
| 		for(auto& blockedPos : tilesBlockedAbsolute) | ||||
| 			gen->setOccupied(blockedPos, ETileType::POSSIBLE); | ||||
| 		 | ||||
| 		if(!shipAccessCandidates.empty() && isAccessibleFromSomewhere(shipyard->appearance, candidateTile)) | ||||
| 		{ | ||||
| 			targetTile = candidateTile; | ||||
| 			break; | ||||
| 			break; //no need to check other offsets as we already found position | ||||
| 		} | ||||
| 		 | ||||
| 		shipAccessCandidates.clear(); //invalidate positions | ||||
| @@ -2146,19 +2141,24 @@ bool CRmgTemplateZone::createShipyard(const int3 & position, si32 guardStrength) | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	placeObject(shipyard, targetTile); | ||||
| 	guardObject(shipyard, guardStrength, false, true); | ||||
| 	 | ||||
| 	for(auto& accessPosition : shipAccessCandidates) | ||||
| 	if(tryToPlaceObjectAndConnectToPath(shipyard, targetTile)==EObjectPlacingResult::SUCCESS) | ||||
| 	{ | ||||
| 		if(connectPath(accessPosition, false)) | ||||
| 		placeObject(shipyard, targetTile); | ||||
| 		guardObject(shipyard, guardStrength, false, true); | ||||
| 	 | ||||
| 		for(auto& accessPosition : shipAccessCandidates) | ||||
| 		{ | ||||
| 			gen->setOccupied(accessPosition, ETileType::FREE); | ||||
| 			return true; | ||||
| 			if(connectPath(accessPosition, false)) | ||||
| 			{ | ||||
| 				gen->setOccupied(accessPosition, ETileType::FREE); | ||||
| 				return true; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	throw rmgException("Cannot find path to shipyard boarding position"); | ||||
| 	logGlobal->warn("Cannot find path to shipyard boarding position"); | ||||
| 	delete shipyard; | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| void CRmgTemplateZone::createTreasures() | ||||
| @@ -2206,7 +2206,12 @@ void CRmgTemplateZone::createTreasures() | ||||
| 			//optimization - don't check tiles which are not allowed | ||||
| 			vstd::erase_if(possibleTiles, [this](const int3 &tile) -> bool | ||||
| 			{ | ||||
| 				return (!gen->isPossible(tile)) || gen->getZoneID(tile)!=getId(); | ||||
| 				//for water area we sholdn't place treasures close to coast | ||||
| 				for(auto & lake : lakes) | ||||
| 					if(vstd::contains(lake.distance, tile) && lake.distance[tile] < 2) | ||||
| 						return true; | ||||
| 				 | ||||
| 				return !gen->isPossible(tile) || gen->getZoneID(tile)!=getId(); | ||||
| 			}); | ||||
|  | ||||
|  | ||||
| @@ -2376,7 +2381,7 @@ void CRmgTemplateZone::drawRoads() | ||||
| 	} | ||||
|  | ||||
| 	gen->getEditManager()->getTerrainSelection().setSelection(tiles); | ||||
| 	gen->getEditManager()->drawRoad(ERoadType::COBBLESTONE_ROAD, &gen->rand); | ||||
| 	gen->getEditManager()->drawRoad(gen->getConfig().defaultRoadType, &gen->rand); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -2386,26 +2391,13 @@ bool CRmgTemplateZone::fill() | ||||
| 	 | ||||
| 	addAllPossibleObjects(); | ||||
| 	 | ||||
| 	if(type==ETemplateZoneType::WATER) | ||||
| 	{ | ||||
| 		initFreeTiles(); | ||||
| 		connectLater(); | ||||
| 		createRequiredObjects(); | ||||
| 		fractalize(); | ||||
| 		createTreasures(); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		//zone center should be always clear to allow other tiles to connect | ||||
| 		initFreeTiles(); | ||||
| 		connectLater(); //ideally this should work after fractalize, but fails | ||||
| 		fractalize(); | ||||
| 		placeMines(); | ||||
| 		createRequiredObjects(); | ||||
| 		createTreasures(); | ||||
| 	} | ||||
| 	 | ||||
| 	gen->dump(false); | ||||
| 	//zone center should be always clear to allow other tiles to connect | ||||
| 	initFreeTiles(); | ||||
| 	connectLater(); //ideally this should work after fractalize, but fails | ||||
| 	fractalize(); | ||||
| 	placeMines(); | ||||
| 	createRequiredObjects(); | ||||
| 	createTreasures(); | ||||
|  | ||||
| 	logGlobal->info("Zone %d filled successfully", id); | ||||
| 	return true; | ||||
| @@ -2613,6 +2605,7 @@ void CRmgTemplateZone::placeObject(CGObjectInstance* object, const int3 &pos, bo | ||||
| 	case Obj::MONOLITH_ONE_WAY_ENTRANCE: | ||||
| 	case Obj::MONOLITH_ONE_WAY_EXIT: | ||||
| 	case Obj::SUBTERRANEAN_GATE: | ||||
| 	case Obj::SHIPYARD: | ||||
| 		{ | ||||
| 			addRoadNode(object->visitablePos()); | ||||
| 		} | ||||
| @@ -2831,11 +2824,11 @@ ObjectInfo CRmgTemplateZone::getRandomObject(CTreasurePileInfo &info, ui32 desir | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (thresholds.empty()) | ||||
| 	if(thresholds.empty()) | ||||
| 	{ | ||||
| 		ObjectInfo oi; | ||||
| 		//Generate pandora Box with gold if the value is extremely high | ||||
| 		if (minValue > 20000) //we don't have object valuable enough | ||||
| 		if(minValue > gen->getConfig().treasureValueLimit) //we don't have object valuable enough | ||||
| 		{ | ||||
| 			oi.generateObject = [minValue]() -> CGObjectInstance * | ||||
| 			{ | ||||
| @@ -2913,17 +2906,15 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
|  | ||||
| 	//prisons | ||||
| 	//levels 1, 5, 10, 20, 30 | ||||
| 	static int prisonExp[] = { 0, 5000, 15000, 90000, 500000 }; | ||||
| 	static int prisonValues[] = { 2500, 5000, 10000, 20000, 30000 }; | ||||
|  | ||||
| 	for (int i = 0; i < 5; i++) | ||||
| 	static int prisonsLevels = std::min(gen->getConfig().prisonExperience.size(), gen->getConfig().prisonValues.size()); | ||||
| 	for(int i = 0; i < prisonsLevels; i++) | ||||
| 	{ | ||||
| 		oi.generateObject = [i, this]() -> CGObjectInstance * | ||||
| 		{ | ||||
| 			std::vector<ui32> possibleHeroes; | ||||
| 			for (int j = 0; j < gen->map->allowedHeroes.size(); j++) | ||||
| 			for(int j = 0; j < gen->map->allowedHeroes.size(); j++) | ||||
| 			{ | ||||
| 				if (gen->map->allowedHeroes[j]) | ||||
| 				if(gen->map->allowedHeroes[j]) | ||||
| 					possibleHeroes.push_back(j); | ||||
| 			} | ||||
|  | ||||
| @@ -2933,7 +2924,7 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
|  | ||||
|  | ||||
| 			obj->subID = hid; //will be initialized later | ||||
| 			obj->exp = prisonExp[i]; | ||||
| 			obj->exp = gen->getConfig().prisonExperience[i]; | ||||
| 			obj->setOwner(PlayerColor::NEUTRAL); | ||||
| 			gen->map->allowedHeroes[hid] = false; //ban this hero | ||||
| 			gen->decreasePrisonsRemaining(); | ||||
| @@ -2942,7 +2933,7 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
| 			return obj; | ||||
| 		}; | ||||
| 		oi.setTemplate(Obj::PRISON, 0, terrainType); | ||||
| 		oi.value = prisonValues[i]; | ||||
| 		oi.value = gen->getConfig().prisonValues[i]; | ||||
| 		oi.probability = 30; | ||||
| 		oi.maxPerZone = gen->getPrisonsRemaning() / 5; //probably not perfect, but we can't generate more prisons than hereos. | ||||
| 		possibleObjects.push_back(oi); | ||||
| @@ -3008,9 +2999,7 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	static const int scrollValues[] = { 500, 2000, 3000, 4000, 5000 }; | ||||
|  | ||||
| 	for (int i = 0; i < 5; i++) | ||||
| 	for(int i = 0; i < gen->getConfig().scrollValues.size(); i++) | ||||
| 	{ | ||||
| 		oi.generateObject = [i, this]() -> CGObjectInstance * | ||||
| 		{ | ||||
| @@ -3030,13 +3019,13 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
| 			return obj; | ||||
| 		}; | ||||
| 		oi.setTemplate(Obj::SPELL_SCROLL, 0, terrainType); | ||||
| 		oi.value = scrollValues[i]; | ||||
| 		oi.value = gen->getConfig().scrollValues[i]; | ||||
| 		oi.probability = 30; | ||||
| 		possibleObjects.push_back(oi); | ||||
| 	} | ||||
|  | ||||
| 	//pandora box with gold | ||||
| 	for (int i = 1; i < 5; i++) | ||||
| 	for(int i = 1; i < 5; i++) | ||||
| 	{ | ||||
| 		oi.generateObject = [i]() -> CGObjectInstance * | ||||
| 		{ | ||||
| @@ -3046,7 +3035,7 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
| 			return obj; | ||||
| 		}; | ||||
| 		oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType); | ||||
| 		oi.value = i * 5000; | ||||
| 		oi.value = i * gen->getConfig().pandoraMultiplierGold; | ||||
| 		oi.probability = 5; | ||||
| 		possibleObjects.push_back(oi); | ||||
| 	} | ||||
| @@ -3062,20 +3051,22 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
| 			return obj; | ||||
| 		}; | ||||
| 		oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType); | ||||
| 		oi.value = i * 6000; | ||||
| 		oi.value = i * gen->getConfig().pandoraMultiplierExperience; | ||||
| 		oi.probability = 20; | ||||
| 		possibleObjects.push_back(oi); | ||||
| 	} | ||||
|  | ||||
| 	//pandora box with creatures | ||||
| 	static const int tierValues[] = { 5000, 7000, 9000, 12000, 16000, 21000, 27000 }; | ||||
| 	const std::vector<int> & tierValues = gen->getConfig().pandoraCreatureValues; | ||||
|  | ||||
| 	auto creatureToCount = [](CCreature * creature) -> int | ||||
| 	auto creatureToCount = [&tierValues](CCreature * creature) -> int | ||||
| 	{ | ||||
| 		if (!creature->AIValue) //bug #2681 | ||||
| 			return 0; //this box won't be generated | ||||
|  | ||||
| 		int actualTier = creature->level > 7 ? 6 : creature->level - 1; | ||||
| 		int actualTier = creature->level > tierValues.size() ? | ||||
| 						 tierValues.size() - 1 : | ||||
| 						 creature->level - 1; | ||||
| 		float creaturesAmount = ((float)tierValues[actualTier]) / creature->AIValue; | ||||
| 		if (creaturesAmount <= 5) | ||||
| 		{ | ||||
| @@ -3142,7 +3133,7 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
| 			return obj; | ||||
| 		}; | ||||
| 		oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType); | ||||
| 		oi.value = (i + 1) * 2500; //5000 - 15000 | ||||
| 		oi.value = (i + 1) * gen->getConfig().pandoraMultiplierSpells; //5000 - 15000 | ||||
| 		oi.probability = 2; | ||||
| 		possibleObjects.push_back(oi); | ||||
| 	} | ||||
| @@ -3171,7 +3162,7 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
| 			return obj; | ||||
| 		}; | ||||
| 		oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType); | ||||
| 		oi.value = 15000; | ||||
| 		oi.value = gen->getConfig().pandoraSpellSchool; | ||||
| 		oi.probability = 2; | ||||
| 		possibleObjects.push_back(oi); | ||||
| 	} | ||||
| @@ -3199,7 +3190,7 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
| 		return obj; | ||||
| 	}; | ||||
| 	oi.setTemplate(Obj::PANDORAS_BOX, 0, terrainType); | ||||
| 	oi.value = 30000; | ||||
| 	oi.value = gen->getConfig().pandoraSpell60; | ||||
| 	oi.probability = 2; | ||||
| 	possibleObjects.push_back(oi); | ||||
|  | ||||
| @@ -3240,7 +3231,7 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
| 			return artInfo; | ||||
| 		}; | ||||
|  | ||||
| 		for (int i = 0; i < std::min((int)creatures.size(), questArtsRemaining - genericSeerHuts); i++) | ||||
| 		for(int i = 0; i < std::min((int)creatures.size(), questArtsRemaining - genericSeerHuts); i++) | ||||
| 		{ | ||||
| 			auto creature = creatures[i]; | ||||
| 			int creaturesAmount = creatureToCount(creature); | ||||
| @@ -3276,15 +3267,13 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
| 			possibleObjects.push_back(oi); | ||||
| 		} | ||||
|  | ||||
| 		static int seerExpGold[] = { 5000, 10000, 15000, 20000 }; | ||||
| 		static int seerValues[] = { 2000, 5333, 8666, 12000 }; | ||||
|  | ||||
| 		for (int i = 0; i < 4; i++) //seems that code for exp and gold reward is similiar | ||||
| 		static int seerLevels = std::min(gen->getConfig().questValues.size(), gen->getConfig().questRewardValues.size()); | ||||
| 		for(int i = 0; i < seerLevels; i++) //seems that code for exp and gold reward is similiar | ||||
| 		{ | ||||
| 			int randomAppearance = *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::SEER_HUT), gen->rand); | ||||
|  | ||||
| 			oi.setTemplate(Obj::SEER_HUT, randomAppearance, terrainType); | ||||
| 			oi.value = seerValues[i]; | ||||
| 			oi.value = gen->getConfig().questValues[i]; | ||||
| 			oi.probability = 10; | ||||
|  | ||||
| 			oi.generateObject = [i, randomAppearance, this, generateArtInfo]() -> CGObjectInstance * | ||||
| @@ -3294,7 +3283,7 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
|  | ||||
| 				obj->rewardType = CGSeerHut::EXPERIENCE; | ||||
| 				obj->rID = 0; //unitialized? | ||||
| 				obj->rVal = seerExpGold[i]; | ||||
| 				obj->rVal = gen->getConfig().questRewardValues[i]; | ||||
|  | ||||
| 				obj->quest->missionType = CQuest::MISSION_ART; | ||||
| 				ArtifactID artid = *RandomGeneratorUtil::nextItem(gen->getQuestArtsRemaning(), gen->rand); | ||||
| @@ -3317,7 +3306,7 @@ void CRmgTemplateZone::addAllPossibleObjects() | ||||
| 				auto obj = (CGSeerHut *) factory->create(ObjectTemplate()); | ||||
| 				obj->rewardType = CGSeerHut::RESOURCES; | ||||
| 				obj->rID = Res::GOLD; | ||||
| 				obj->rVal = seerExpGold[i]; | ||||
| 				obj->rVal = gen->getConfig().questRewardValues[i]; | ||||
|  | ||||
| 				obj->quest->missionType = CQuest::MISSION_ART; | ||||
| 				ArtifactID artid = *RandomGeneratorUtil::nextItem(gen->getQuestArtsRemaning(), gen->rand); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user