Преглед изворни кода

Proof of concept with OH3 obstacles

Tomasz Zieliński пре 1 година
родитељ
комит
b1a5693612

+ 2 - 0
lib/CMakeLists.txt

@@ -121,6 +121,7 @@ set(lib_MAIN_SRCS
 	mapObjects/IObjectInterface.cpp
 	mapObjects/MiscObjects.cpp
 	mapObjects/ObjectTemplate.cpp
+	mapObjects/ObstacleSetHandler.cpp
 
 	mapping/CDrawRoadsOperation.cpp
 	mapping/CMap.cpp
@@ -482,6 +483,7 @@ set(lib_MAIN_HEADERS
 	mapObjects/MapObjects.h
 	mapObjects/MiscObjects.h
 	mapObjects/ObjectTemplate.h
+	mapObjects/ObstacleSetHandler.h
 
 	mapping/CDrawRoadsOperation.h
 	mapping/CMapDefines.h

+ 2 - 0
lib/VCMI_Lib.cpp

@@ -37,6 +37,7 @@
 #include "rmg/CRmgTemplateStorage.h"
 #include "mapObjectConstructors/CObjectClassesHandler.h"
 #include "mapObjects/CObjectHandler.h"
+#include "mapObjects/ObstacleSetHandler.h"
 #include "mapping/CMapEditManager.h"
 #include "ScriptHandler.h"
 #include "BattleFieldHandler.h"
@@ -223,6 +224,7 @@ void LibClasses::init(bool onlyEssential)
 	createHandler(arth, "Artifact", pomtime);
 	createHandler(creh, "Creature", pomtime);
 	createHandler(townh, "Town", pomtime);
+	createHandler(biomeHandler, "Obstacle set", pomtime);
 	createHandler(objh, "Object", pomtime);
 	createHandler(objtypeh, "Object types information", pomtime);
 	createHandler(spellh, "Spell", pomtime);

+ 3 - 0
lib/VCMI_Lib.h

@@ -23,6 +23,7 @@ class CSkillHandler;
 class CBuildingHandler;
 class CObjectHandler;
 class CObjectClassesHandler;
+class ObstacleSetHandler;
 class CTownHandler;
 class CGeneralTextHandler;
 class CModHandler;
@@ -85,6 +86,7 @@ public:
 	std::shared_ptr<CCreatureHandler> creh;
 	std::shared_ptr<CSpellHandler> spellh;
 	std::shared_ptr<CSkillHandler> skillh;
+	// TODO: Remove ObjectHandler altogether?
 	std::shared_ptr<CObjectHandler> objh;
 	std::shared_ptr<CObjectClassesHandler> objtypeh;
 	std::shared_ptr<CTownHandler> townh;
@@ -99,6 +101,7 @@ public:
 	std::shared_ptr<BattleFieldHandler> battlefieldsHandler;
 	std::shared_ptr<ObstacleHandler> obstacleHandler;
 	std::shared_ptr<GameSettings> settingsHandler;
+	std::shared_ptr<ObstacleSetHandler> biomeHandler;
 
 #if SCRIPTING_ENABLED
 	std::shared_ptr<scripting::ScriptHandler> scriptHandler;

+ 39 - 0
lib/constants/EntityIdentifiers.h

@@ -457,7 +457,46 @@ public:
 		WHIRLPOOL = 111,
 		WINDMILL = 112,
 		WITCH_HUT = 113,
+		BRUSH = 114, // TODO: How does it look like?
+		BUSH = 115,
+		CACTUS = 116,
+		CANYON = 117,
+		CRATER = 118,
+		DEAD_VEGETATION = 119,
+		FLOWERS = 120,
+		FROZEN_LAKE = 121,
+		HEDGE = 122,
+		HILL = 123,
 		HOLE = 124,
+		KELP = 125,
+		LAKE = 126,
+		LAVA_FLOW = 127,
+		LAVA_LAKE = 128,
+		MUSHROOMS = 129,
+		LOG = 130,
+		MANDRAKE = 131,
+		MOSS = 132,
+		MOUND = 133,
+		MOUNTAIN = 134,
+		OAK_TREES = 135,
+		OUTCROPPING = 136,
+		PINE_TREES = 137,
+		PLANT = 138,
+		ROCK = 147,
+		SAND_DUNE = 148,
+		SAND_PIT = 149,
+		SHRUB = 150,
+		SKULL = 151,
+		STALAGMITE = 152,
+		STUMP = 153,
+		TAR_PIT = 154,
+		TREES = 155,
+		VINE = 156,
+		VOLCANIC_VENT = 157,
+		VOLCANO = 158,
+		WILLOW_TREES = 159,
+		YUCCA_TREES = 160,
+		REEF = 161,
 		RANDOM_MONSTER_L5 = 162,
 		RANDOM_MONSTER_L6 = 163,
 		RANDOM_MONSTER_L7 = 164,

+ 40 - 0
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -34,6 +34,7 @@
 #include "../mapObjects/MiscObjects.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../mapObjects/CGTownInstance.h"
+#include "../mapObjects/ObstacleSetHandler.h"
 #include "../modding/IdentifierStorage.h"
 #include "../modding/CModHandler.h"
 #include "../modding/ModScope.h"
@@ -107,6 +108,8 @@ std::vector<JsonNode> CObjectClassesHandler::loadLegacyData()
 	auto totalNumber = static_cast<size_t>(parser.readNumber()); // first line contains number of objects to read and nothing else
 	parser.endLine();
 
+	std::map<TerrainId, std::map<MapObjectID, std::map<MapObjectSubID, ObstacleSet>>> obstacleSets;
+
 	for (size_t i = 0; i < totalNumber; i++)
 	{
 		auto tmpl = std::make_shared<ObjectTemplate>();
@@ -116,7 +119,44 @@ std::vector<JsonNode> CObjectClassesHandler::loadLegacyData()
 
 		std::pair key(tmpl->id, tmpl->subid);
 		legacyTemplates.insert(std::make_pair(key, tmpl));
+
+		// Populate ObstacleSetHandler on our way
+		if (!tmpl->isVisitable())
+		{
+			// Create obstacle sets. Group by terrain for now
+
+			for (auto terrain : tmpl->getAllowedTerrains())
+			{
+				auto &obstacleMap = obstacleSets[terrain][tmpl->id];
+				auto it = obstacleMap.find(tmpl->subid);
+				if (it == obstacleMap.end())
+				{
+					// FIXME: This means this set will be available only on one terrain
+					auto type = VLC->biomeHandler->convertObstacleClass(tmpl->id);
+					auto newIt = obstacleMap.insert(std::make_pair(tmpl->subid, ObstacleSet(type, terrain)));
+					newIt.first->second.addObstacle(tmpl);
+				}
+				else
+				{
+					it->second.addObstacle(tmpl);
+				}
+			}
+		}
+	}
+
+	size_t count = 0;
+	for (auto terrain : obstacleSets)
+	{
+		for (auto obstacleType : terrain.second)
+		{
+			for (auto obstacleSet : obstacleType.second)
+			{
+				VLC->biomeHandler->addObstacleSet(obstacleSet.second);
+				count++;
+			}
+		}
 	}
+	logGlobal->info("Obstacle sets loaded: %d", count);
 
 	objects.resize(256);
 

+ 153 - 0
lib/mapObjects/ObstacleSetHandler.cpp

@@ -0,0 +1,153 @@
+/*
+ * ObstacleSetHandler.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 "ObstacleSetHandler.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+ObstacleSet::ObstacleSet(EObstacleType type, TerrainId terrain):
+	type(type),
+	terrain(terrain)
+{
+}
+
+void ObstacleSet::addObstacle(std::shared_ptr<const ObjectTemplate> obstacle)
+{
+	obstacles.push_back(obstacle);
+}
+
+ObstacleSetFilter::ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain = TerrainId::ANY_TERRAIN):
+	allowedTypes(allowedTypes),
+	terrain(terrain)
+{
+}
+
+ObstacleSetFilter::ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain = TerrainId::ANY_TERRAIN):
+	allowedTypes({allowedType}),
+	terrain(terrain)
+{
+}
+
+bool ObstacleSetFilter::filter(const ObstacleSet &set) const
+{
+	return (set.getTerrain() == terrain) || (terrain == TerrainId::ANY_TERRAIN);
+}
+
+TerrainId ObstacleSetFilter::getTerrain() const
+{
+	return terrain;
+}
+
+TerrainId ObstacleSet::getTerrain() const
+{
+	return terrain;
+}
+
+ObstacleSet::EObstacleType ObstacleSet::getType() const
+{
+	return type;
+}
+
+std::vector<std::shared_ptr<const ObjectTemplate>> ObstacleSet::getObstacles() const
+{
+	return obstacles;
+}
+
+ObstacleSet::EObstacleType ObstacleSetHandler::convertObstacleClass(MapObjectID id)
+{
+	switch (id)
+	{
+		case Obj::MOUNTAIN:
+		case Obj::SAND_DUNE:
+		case Obj::VOLCANIC_VENT:
+		case Obj::VOLCANO:
+		case Obj::REEF:
+			return ObstacleSet::MOUNTAINS;
+		case Obj::OAK_TREES:
+		case Obj::PINE_TREES:
+		case Obj::TREES:
+		case Obj::DEAD_VEGETATION:
+		case Obj::HEDGE:
+		case Obj::KELP:
+		case Obj::WILLOW_TREES:
+		case Obj::YUCCA_TREES:
+			return ObstacleSet::TREES;
+		case Obj::FROZEN_LAKE:
+		case Obj::LAKE:
+		case Obj::LAVA_FLOW:
+		case Obj::LAVA_LAKE:
+			return ObstacleSet::LAKES;
+		case Obj::CANYON:
+		case Obj::CRATER:
+		case Obj::SAND_PIT:
+		case Obj::TAR_PIT:
+			return ObstacleSet::CRATERS;
+		case Obj::HILL:
+		case Obj::MOUND:
+		case Obj::OUTCROPPING:
+		case Obj::ROCK:
+		case Obj::STALAGMITE:
+			return ObstacleSet::ROCKS;
+		case Obj::BUSH:
+		case Obj::CACTUS:
+		case Obj::FLOWERS:
+		case Obj::MUSHROOMS:
+		case Obj::LOG:
+		case Obj::MANDRAKE:
+		case Obj::MOSS:
+		case Obj::PLANT:
+		case Obj::SHRUB:
+		case Obj::STUMP:
+		case Obj::VINE:
+			return ObstacleSet::PLANTS;
+		case Obj::SKULL:
+			return ObstacleSet::ANIMALS;
+		default:
+			return ObstacleSet::OTHER;
+	}
+}
+
+std::vector<ObstacleSet::EObstacleType> ObstacleSetFilter::getAllowedTypes() const
+{
+	return allowedTypes;
+}
+
+void ObstacleSetHandler::addObstacleSet(const ObstacleSet &os)
+{
+	obstacleSets[os.getType()].push_back(os);
+}
+
+TObstacleTypes ObstacleSetHandler::getObstacles( const ObstacleSetFilter &filter) const
+{
+	// TODO: Handle multiple terrains for one obstacle set?
+	auto terrainType = filter.getTerrain();
+
+	TObstacleTypes result;
+
+	for (const auto &allowedType : filter.getAllowedTypes())
+	{
+		auto it = obstacleSets.find(allowedType);
+		if(it != obstacleSets.end())
+		{
+			for (const auto &os : it->second)
+			{
+				if (filter.filter(os))
+				{
+					result.push_back(os);
+				}
+			}
+		}
+	}
+	return result;
+}
+
+VCMI_LIB_NAMESPACE_END
+

+ 99 - 0
lib/mapObjects/ObstacleSetHandler.h

@@ -0,0 +1,99 @@
+/*
+ * ObstacleSetHandler.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 "../GameConstants.h"
+#include "../constants/EntityIdentifiers.h"
+#include "../IHandlerBase.h"
+#include "../json/JsonNode.h"
+#include "ObjectTemplate.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE ObstacleSet
+{
+public:
+
+	// TODO: Create string constants for these
+
+	enum EObstacleType
+	{
+		MOUNTAINS,
+		TREES,
+		LAKES, // Inluding dry or lava lakes
+		CRATERS, // Chasms, Canyons, etc.
+		ROCKS,
+		PLANTS, // Flowers, cacti, mushrooms, logs, shrubs, etc.
+		STRUCTURES, // Buildings, ruins, etc.
+		ANIMALS, // Living, or bones
+		OTHER // Crystals, shipwrecks, barrels, etc.
+	};
+
+	explicit ObstacleSet(EObstacleType type, TerrainId terrain);
+
+	void addObstacle(std::shared_ptr<const ObjectTemplate> obstacle);
+	std::vector<std::shared_ptr<const ObjectTemplate>> getObstacles() const;
+
+	EObstacleType getType() const;
+	TerrainId getTerrain() const;
+
+private:
+	EObstacleType type;
+	TerrainId terrain;
+	std::vector<std::shared_ptr<const ObjectTemplate>> obstacles;
+};
+
+typedef std::vector<ObstacleSet> TObstacleTypes;
+
+class DLL_LINKAGE ObstacleSetFilter
+{
+public:
+	ObstacleSetFilter(ObstacleSet::EObstacleType allowedType, TerrainId terrain);
+	ObstacleSetFilter(std::vector<ObstacleSet::EObstacleType> allowedTypes, TerrainId terrain);
+
+	bool filter(const ObstacleSet &set) const;
+
+	std::vector<ObstacleSet::EObstacleType> getAllowedTypes() const;
+	TerrainId getTerrain() const;
+
+private:
+	std::vector<ObstacleSet::EObstacleType> allowedTypes;
+// TODO: Filter by faction, alignment, surface/underground, etc.
+	const TerrainId terrain;
+};
+
+// TODO: Instantiate ObstacleSetHandler
+class DLL_LINKAGE ObstacleSetHandler : public IHandlerBase, boost::noncopyable
+{
+public:
+
+	ObstacleSetHandler() = default;
+	~ObstacleSetHandler() = default;
+
+	// FIXME: Not needed at all
+	std::vector<JsonNode> loadLegacyData() override {return {};};
+	virtual void loadObject(std::string scope, std::string name, const JsonNode & data) override {};
+	virtual void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override {};
+
+	ObstacleSet::EObstacleType convertObstacleClass(MapObjectID id);
+
+	// TODO: Populate obstacleSets with all the obstacle sets from the game data
+
+	void addObstacleSet(const ObstacleSet &set);
+	
+	TObstacleTypes getObstacles(const ObstacleSetFilter &filter) const;
+
+private:
+
+	std::map<ObstacleSet::EObstacleType, std::vector<ObstacleSet>> obstacleSets;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 165 - 0
lib/mapping/ObstacleProxy.cpp

@@ -15,6 +15,7 @@
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../mapObjects/CGObjectInstance.h"
 #include "../mapObjects/ObjectTemplate.h"
+#include "../mapObjects/ObstacleSetHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -46,6 +47,170 @@ void ObstacleProxy::collectPossibleObstacles(TerrainId terrain)
 	});
 }
 
+bool ObstacleProxy::prepareBiome(TerrainId terrain, CRandomGenerator & rand)
+{
+	// FIXME: All the mountains have same ID and mostly same subID, how to differentiate them?
+
+	bool isPrepared = false;
+
+	possibleObstacles.clear();
+
+	// TODO: Where to parse these sets?
+
+	std::vector<ObstacleSet> obstacleSets;
+
+	size_t selectedSets = 0;
+	size_t smallSets = 0;
+	const size_t MINIMUM_SETS = 6;
+	const size_t MAXIMUM_SETS = 9;
+	const size_t MIN_SMALL_SETS = 3;
+	const size_t MAX_SMALL_SETS = 5;
+
+	TObstacleTypes mountainSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter(ObstacleSet::EObstacleType::MOUNTAINS, terrain));
+
+	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", terrain.encode(terrain.getNum()));
+		// FIXME: Do we ever want to generate obstacles without any mountains?
+	}
+
+	TObstacleTypes treeSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter(ObstacleSet::EObstacleType::TREES, terrain));
+
+	// 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
+	TObstacleTypes largeSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter({ObstacleSet::EObstacleType::LAKES, ObstacleSet::EObstacleType::CRATERS},
+	terrain));
+
+	// 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());
+	}
+
+	TObstacleTypes rockSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter(ObstacleSet::EObstacleType::ROCKS, terrain));
+
+	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);
+
+	TObstacleTypes plantSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter(ObstacleSet::EObstacleType::PLANTS, terrain));
+
+	// 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 smallSetsCount = rand.nextInt(MIN_SMALL_SETS, maxSmallSets);
+
+	TObstacleTypes smallObstacleSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter({ObstacleSet::EObstacleType::STRUCTURES, ObstacleSet::EObstacleType::ANIMALS},
+	terrain));
+	RandomGeneratorUtil::randomShuffle(smallObstacleSets, rand);
+
+	TObstacleTypes otherSets = VLC->biomeHandler->getObstacles(ObstacleSetFilter(ObstacleSet::EObstacleType::OTHER,
+	terrain));
+	RandomGeneratorUtil::randomShuffle(otherSets, rand);
+
+	while (smallSetsCount > 0)
+	{
+		if (!smallObstacleSets.empty())
+		{
+			obstacleSets.push_back(smallObstacleSets.back());
+			smallObstacleSets.pop_back();
+			selectedSets++;
+			smallSetsCount--;
+			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 (smallSetsCount > 0)
+		{
+			// Fill with whatever's left
+			if (!otherSets.empty())
+			{
+				obstacleSets.push_back(otherSets.back());
+				otherSets.pop_back();
+				selectedSets++;
+				smallSetsCount--;
+
+				logGlobal->info("Added set of other obstacles");
+			}
+		}
+	}
+
+	// Copy this set to our possible obstacles
+	
+	if (selectedSets >= MINIMUM_SETS)
+	{
+		obstaclesBySize.clear();
+		for (const auto & os : obstacleSets)
+		{
+			for (const auto & temp : os.getObstacles())
+			{
+				if(temp->getBlockMapOffset().valid())
+				{
+					obstaclesBySize[temp->getBlockedOffsets().size()].push_back(temp);
+				}
+			}
+		}
+
+		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
+		});
+		
+		return true;
+	}
+	else
+	{
+		return false; 			// Proceed with old method
+	}
+
+	return isPrepared;
+}
+
 void ObstacleProxy::addBlockedTile(const int3& tile)
 {
 	blockedArea.add(tile);

+ 1 - 0
lib/mapping/ObstacleProxy.h

@@ -29,6 +29,7 @@ public:
 	virtual ~ObstacleProxy() = default;
 
 	void collectPossibleObstacles(TerrainId terrain);
+	bool prepareBiome(TerrainId terrain, CRandomGenerator & rand);
 
 	void addBlockedTile(const int3 & tile);
 

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

@@ -34,7 +34,16 @@ void ObstaclePlacer::process()
 	if(!manager)
 		return;
 
-	collectPossibleObstacles(zone.getTerrainType());
+	if (!prepareBiome(zone.getTerrainType(), zone.getRand()))
+	{
+		logGlobal->warn("Failed to prepare biome, using all possible obstacles");
+		// Use all if we fail to create proper biome
+		collectPossibleObstacles(zone.getTerrainType());
+	}
+	else
+	{
+		logGlobal->info("Biome prepared successfully for zone %d", zone.getId());
+	}
 	
 	{
 		auto area = zone.area();