1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Proof of concept with OH3 obstacles

This commit is contained in:
Tomasz Zieliński 2024-04-04 21:39:01 +02:00
parent c2f160326c
commit b1a5693612
10 changed files with 514 additions and 1 deletions

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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,

View File

@ -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,8 +119,45 @@ 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);
std::vector<JsonNode> ret(dataSize);// create storage for 256 objects

View File

@ -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

View File

@ -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

View File

@ -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);

View File

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

View File

@ -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();