| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 | /* * ObstacleProxy.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 "ObstacleProxy.h"#include "../mapping/CMap.h"#include "../mapObjectConstructors/AObjectTypeHandler.h"#include "../mapObjectConstructors/CObjectClassesHandler.h"#include "../mapObjects/CGObjectInstance.h"#include "../mapObjects/ObjectTemplate.h"#include "../mapObjects/ObstacleSetHandler.h"VCMI_LIB_NAMESPACE_BEGINvoid ObstacleProxy::collectPossibleObstacles(TerrainId terrain){	//get all possible obstacles for this terrain	for(auto primaryID : VLC->objtypeh->knownObjects())	{		for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))		{			auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);			if(handler->isStaticObject())			{				for(const auto & temp : handler->getTemplates())				{					if(temp->canBePlacedAt(terrain) && temp->getBlockMapOffset().valid())						obstaclesBySize[temp->getBlockedOffsets().size()].push_back(temp);				}			}		}	}	sortObstacles();}void ObstacleProxy::sortObstacles(){	for(const auto & o : obstaclesBySize)	{		possibleObstacles.emplace_back(o);	}	boost::sort(possibleObstacles, [](const ObstaclePair &p1, const ObstaclePair &p2) -> bool	{		return p1.first > p2.first; //bigger obstacles first	});}bool ObstacleProxy::prepareBiome(const ObstacleSetFilter & filter, CRandomGenerator & rand){	possibleObstacles.clear();	std::vector<std::shared_ptr<ObstacleSet>> obstacleSets;	size_t selectedSets = 0;	const size_t MINIMUM_SETS = 3; // Original Lava has only 4 types of sets	const size_t MAXIMUM_SETS = 9;	const size_t MIN_SMALL_SETS = 3;	const size_t MAX_SMALL_SETS = 5;	auto terrain = filter.getTerrain();	auto localFilter = filter;	localFilter.setType(ObstacleSet::EObstacleType::MOUNTAINS);	TObstacleTypes mountainSets = VLC->biomeHandler->getObstacles(localFilter);	if (!mountainSets.empty())	{		obstacleSets.push_back(*RandomGeneratorUtil::nextItem(mountainSets, rand));		selectedSets++;		logGlobal->info("Mountain set added");	}	else	{		logGlobal->warn("No mountain sets found for terrain %s", TerrainId::encode(terrain.getNum()));		// FIXME: Do we ever want to generate obstacles without any mountains?	}	localFilter.setType(ObstacleSet::EObstacleType::TREES);	TObstacleTypes treeSets = VLC->biomeHandler->getObstacles(localFilter);	// 1 or 2 tree sets	size_t treeSetsCount = std::min<size_t>(treeSets.size(), rand.nextInt(1, 2));	for (size_t i = 0; i < treeSetsCount; i++)	{		obstacleSets.push_back(*RandomGeneratorUtil::nextItem(treeSets, rand));		selectedSets++;	}	logGlobal->info("Added %d tree sets", treeSetsCount);	// Some obstacle types may be completely missing from water, but it's not a problem	localFilter.setTypes({ObstacleSet::EObstacleType::LAKES, ObstacleSet::EObstacleType::CRATERS});	TObstacleTypes largeSets = VLC->biomeHandler->getObstacles(localFilter);	// We probably don't want to have lakes and craters at the same time, choose one of them	if (!largeSets.empty())	{		obstacleSets.push_back(*RandomGeneratorUtil::nextItem(largeSets, rand));		selectedSets++;		// TODO: Convert to string		logGlobal->info("Added large set of type %s", obstacleSets.back()->getType());	}	localFilter.setType(ObstacleSet::EObstacleType::ROCKS);	TObstacleTypes rockSets = VLC->biomeHandler->getObstacles(localFilter);	size_t rockSetsCount = std::min<size_t>(rockSets.size(), rand.nextInt(1, 2));	for (size_t i = 0; i < rockSetsCount; i++)	{		obstacleSets.push_back(*RandomGeneratorUtil::nextItem(rockSets, rand));		selectedSets++;	}	logGlobal->info("Added %d rock sets", rockSetsCount);	localFilter.setType(ObstacleSet::EObstacleType::PLANTS);	TObstacleTypes plantSets = VLC->biomeHandler->getObstacles(localFilter);	// 1 or 2 sets (3 - rock sets)	size_t plantSetsCount = std::min<size_t>(plantSets.size(), rand.nextInt(1, std::max<size_t>(3 - rockSetsCount, 2)));	for (size_t i = 0; i < plantSetsCount; i++)	{		{			obstacleSets.push_back(*RandomGeneratorUtil::nextItem(plantSets, rand));			selectedSets++;		}	}	logGlobal->info("Added %d plant sets", plantSetsCount);	//3 to 5 of total small sets (rocks, plants, structures, animals and others)	//This gives total of 6 to 9 different sets	size_t maxSmallSets = std::min<size_t>(MAX_SMALL_SETS, std::max(MIN_SMALL_SETS, MAXIMUM_SETS - selectedSets));	size_t smallSets = rand.nextInt(MIN_SMALL_SETS, maxSmallSets);	localFilter.setTypes({ObstacleSet::EObstacleType::STRUCTURES, ObstacleSet::EObstacleType::ANIMALS});	TObstacleTypes smallObstacleSets = VLC->biomeHandler->getObstacles(localFilter);	RandomGeneratorUtil::randomShuffle(smallObstacleSets, rand);	localFilter.setType(ObstacleSet::EObstacleType::OTHER);	TObstacleTypes otherSets = VLC->biomeHandler->getObstacles(localFilter);	RandomGeneratorUtil::randomShuffle(otherSets, rand);	while (smallSets > 0)	{		if (!smallObstacleSets.empty())		{			obstacleSets.push_back(smallObstacleSets.back());			smallObstacleSets.pop_back();			selectedSets++;			smallSets--;			logGlobal->info("Added small set of type %s", obstacleSets.back()->getType());		}		else if(otherSets.empty())		{			logGlobal->warn("No other sets found for terrain %s", terrain.encode(terrain.getNum()));			break;		}		if (smallSets > 0)		{			// Fill with whatever's left			if (!otherSets.empty())			{				obstacleSets.push_back(otherSets.back());				otherSets.pop_back();				selectedSets++;				smallSets--;				logGlobal->info("Added set of other obstacles");			}		}	}	// Copy this set to our possible obstacles	if (selectedSets >= MINIMUM_SETS ||		(terrain == TerrainId::WATER && selectedSets > 0))	{		obstaclesBySize.clear();		for (const auto & os : obstacleSets)		{			for (const auto & temp : os->getObstacles())			{				if(temp->getBlockMapOffset().valid())				{					obstaclesBySize[temp->getBlockedOffsets().size()].push_back(temp);				}			}		}		sortObstacles();		return true;	}	else	{		return false; // Proceed with old method	}}void ObstacleProxy::addBlockedTile(const int3& tile){	blockedArea.add(tile);}void ObstacleProxy::setBlockedArea(const rmg::Area& area){	blockedArea = area;}void ObstacleProxy::clearBlockedArea(){	blockedArea.clear();}bool ObstacleProxy::isProhibited(const rmg::Area& objArea) const{	return false;};int ObstacleProxy::getWeightedObjects(const int3 & tile, CRandomGenerator & rand, IGameCallback * cb, std::list<rmg::Object> & allObjects, std::vector<std::pair<rmg::Object*, int3>> & weightedObjects){	int maxWeight = std::numeric_limits<int>::min();	for(auto & possibleObstacle : possibleObstacles)	{		if(!possibleObstacle.first)			continue;		auto shuffledObstacles = possibleObstacle.second;		RandomGeneratorUtil::randomShuffle(shuffledObstacles, rand);		for(const auto & temp : shuffledObstacles)		{			auto handler = VLC->objtypeh->getHandlerFor(temp->id, temp->subid);			auto * obj = handler->create(nullptr, temp);			allObjects.emplace_back(*obj);			rmg::Object * rmgObject = &allObjects.back();			for(const auto & offset : obj->getBlockedOffsets())			{				auto newPos = tile - offset;				if(!isInTheMap(newPos))					continue;				rmgObject->setPosition(newPos);				bool isInTheMapEntirely = true;				for (const auto & t : rmgObject->getArea().getTiles())				{					if (!isInTheMap(t))					{						isInTheMapEntirely = false;						break;					}				}				if (!isInTheMapEntirely)				{					continue;				}				if(isProhibited(rmgObject->getArea()))					continue;				int coverageBlocked = 0;				int coveragePossible = 0;				//do not use area intersection in optimization purposes				for(const auto & t : rmgObject->getArea().getTilesVector())				{					auto coverage = verifyCoverage(t);					if(coverage.first)						++coverageBlocked;					if(coverage.second)						++coveragePossible;				}				int coverageOverlap = possibleObstacle.first - coverageBlocked - coveragePossible;				int weight = possibleObstacle.first + coverageBlocked - coverageOverlap * possibleObstacle.first;				assert(coverageOverlap >= 0);				if(weight > maxWeight)				{					weightedObjects.clear();					maxWeight = weight;					weightedObjects.emplace_back(rmgObject, rmgObject->getPosition());					if(weight > 0)						break;				}				else if(weight == maxWeight)					weightedObjects.emplace_back(rmgObject, rmgObject->getPosition());			}		}		if(maxWeight > 0)			break;	}	return maxWeight;}std::set<CGObjectInstance*> ObstacleProxy::createObstacles(CRandomGenerator & rand, IGameCallback * cb){	//reverse order, since obstacles begin in bottom-right corner, while the map coordinates begin in top-left	auto blockedTiles = blockedArea.getTilesVector();	int tilePos = 0;	std::set<CGObjectInstance*> objs;	while(!blockedArea.empty() && tilePos < blockedArea.getTilesVector().size())	{		auto tile = blockedArea.getTilesVector()[tilePos];		std::list<rmg::Object> allObjects;		std::vector<std::pair<rmg::Object*, int3>> weightedObjects;		int maxWeight = getWeightedObjects(tile, rand, cb, allObjects, weightedObjects);		if(weightedObjects.empty())		{			tilePos += 1;			continue;		}		auto objIter = RandomGeneratorUtil::nextItem(weightedObjects, rand);		objIter->first->setPosition(objIter->second);		placeObject(*objIter->first, objs);		blockedArea.subtract(objIter->first->getArea());		tilePos = 0;		postProcess(*objIter->first);		if(maxWeight < 0)			logGlobal->warn("Placed obstacle with negative weight at %s", objIter->second.toString());		for(auto & o : allObjects)		{			if(&o != objIter->first)				o.clear();		}	}	return objs;}//FIXME: Only editor placer obstacles directlyvoid ObstacleProxy::finalInsertion(CMapEditManager * manager, std::set<CGObjectInstance*> & instances){	manager->insertObjects(instances); //insert as one operation - for undo purposes}std::pair<bool, bool> ObstacleProxy::verifyCoverage(const int3 & t) const{	return {blockedArea.contains(t), false};}void ObstacleProxy::placeObject(rmg::Object & object, std::set<CGObjectInstance*> & instances){	for (auto * instance : object.instances())	{		instances.insert(&instance->object());	}}EditorObstaclePlacer::EditorObstaclePlacer(CMap* map):	map(map){}bool EditorObstaclePlacer::isInTheMap(const int3& tile){	return map->isInTheMap(tile);}std::set<CGObjectInstance*> EditorObstaclePlacer::placeObstacles(CRandomGenerator & rand){	auto obstacles = createObstacles(rand, map->cb);	finalInsertion(map->getEditManager(), obstacles);	return obstacles;}VCMI_LIB_NAMESPACE_END
 |