1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

Implemented A* algorithm to draw shortest roads - but not correct roads yet.

This commit is contained in:
DjWarmonger 2015-05-25 16:37:57 +02:00
commit 6d502ef1a1
13 changed files with 761 additions and 75 deletions

View File

@ -482,6 +482,23 @@ enum class ETeleportChannelType
MIXED
};
namespace ERiverType
{
enum ERiverType
{
NO_RIVER, CLEAR_RIVER, ICY_RIVER, MUDDY_RIVER, LAVA_RIVER
};
}
namespace ERoadType
{
enum ERoadType
{
NO_ROAD, DIRT_ROAD, GRAVEL_ROAD, COBBLESTONE_ROAD
};
}
class Obj
{
public:

View File

@ -255,6 +255,8 @@
<Unit filename="mapObjects/ObjectTemplate.h" />
<Unit filename="mapping/CCampaignHandler.cpp" />
<Unit filename="mapping/CCampaignHandler.h" />
<Unit filename="mapping/CDrawRoadsOperation.cpp" />
<Unit filename="mapping/CDrawRoadsOperation.h" />
<Unit filename="mapping/CMap.cpp" />
<Unit filename="mapping/CMap.h" />
<Unit filename="mapping/CMapEditManager.cpp" />

View File

@ -231,6 +231,7 @@
<ClCompile Include="mapping\CMapEditManager.cpp" />
<ClCompile Include="mapping\MapFormatH3M.cpp" />
<ClCompile Include="mapping\MapFormatJson.cpp" />
<ClCompile Include="mapping\CDrawRoadsOperation.cpp" />
<ClCompile Include="registerTypes\RegisterTypes.cpp" />
<ClCompile Include="registerTypes\TypesClientPacks1.cpp" />
<ClCompile Include="registerTypes\TypesClientPacks2.cpp" />

View File

@ -0,0 +1,353 @@
/*
* CDrawRoadsOperation.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 "CDrawRoadsOperation.h"
const std::vector<CDrawRoadsOperation::RoadPattern> CDrawRoadsOperation::patterns =
{
//single tile. fall-back pattern
{
{
"-","-","-",
"-","+","-",
"-","-","-"
},
{14,14},
{9,9},
false,
false
},
//Road straight with angle
{
{
"?","-","+",
"-","+","+",
"+","+","?"
},
{2,5},
{-1,-1},
true,
true
},
//Turn
{
{
"?","-","?",
"-","+","+",
"?","+","?"
},
{0,1},
{0,3},
true,
true
},
//Dead end horizontal
{
{
"?","-","?",
"-","+","+",
"?","-","?"
},
{15,15},{11,12},
true,
false
},
//Dead end vertical
{
{
"?","-","?",
"-","+","-",
"?","+","?"
},
{14,14},{9,10},
false,
true
},
//T-cross horizontal
{
{
"?","+","?",
"-","+","+",
"?","+","?"
},
{6,7},{7,8},
true,
false
},
//T-cross vertical
{
{
"?","-","?",
"+","+","+",
"?","+","?"
},
{8,9},{5,6},
false,
true
},
//Straight Horizontal
{
{
"?","-","?",
"+","+","+",
"?","-","?"
},
{12,13},{11,12},
false,
false
},
//Straight Vertical
{
{
"?","+","?",
"-","+","-",
"?","+","?"
},
{10,11},{9,10},
false,
false
},
//X-cross
{
{
"?","+","?",
"+","+","+",
"?","+","?"
},
{16,16},{4,4},
false,
false
}
};
static bool ruleIsNone(const std::string & rule)
{
return rule == "-";
}
static bool ruleIsSomething(const std::string & rule)
{
return rule == "+";
}
static bool ruleIsAny(const std::string & rule)
{
return rule == "?";
}
///CDrawRoadsOperation
CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, ERoadType::ERoadType roadType, CRandomGenerator * gen):
CMapOperation(map),terrainSel(terrainSel), roadType(roadType), gen(gen)
{
}
void CDrawRoadsOperation::execute()
{
std::set<int3> invalidated;
for(const auto & pos : terrainSel.getSelectedItems())
{
auto & tile = map->getTile(pos);
tile.roadType = roadType;
auto rect = extendTileAroundSafely(pos);
rect.forEach([&invalidated](const int3 & pos)
{
invalidated.insert(pos);
});
}
updateTiles(invalidated);
}
void CDrawRoadsOperation::undo()
{
//TODO
}
void CDrawRoadsOperation::redo()
{
//TODO
}
std::string CDrawRoadsOperation::getLabel() const
{
return "Draw Roads";
}
bool CDrawRoadsOperation::canApplyPattern(const RoadPattern & pattern) const
{
//TODO: this method should be virtual for river support
return pattern.roadMapping.first >= 0;
}
void CDrawRoadsOperation::flipPattern(RoadPattern& pattern, int flip) const
{
//todo: use cashing here and also in terrain patterns
if(flip == 0)
{
return;
}
if(flip == FLIP_PATTERN_HORIZONTAL || flip == FLIP_PATTERN_BOTH)
{
for(int i = 0; i < 3; ++i)
{
int y = i * 3;
std::swap(pattern.data[y], pattern.data[y + 2]);
}
}
if(flip == FLIP_PATTERN_VERTICAL || flip == FLIP_PATTERN_BOTH)
{
for(int i = 0; i < 3; ++i)
{
std::swap(pattern.data[i], pattern.data[6 + i]);
}
}
}
bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const
{
return tile.roadType != ERoadType::NO_ROAD; //TODO: this method should be virtual for river support
}
void CDrawRoadsOperation::updateTiles(std::set<int3> & invalidated)
{
for(int3 coord : invalidated)
{
TerrainTile & tile = map->getTile(coord);
ValidationResult result(false);
if(!needUpdateTile(tile))
continue;
int bestPattern = -1;
for(int k = 0; k < patterns.size(); ++k)
{
result = validateTile(patterns[k], coord);
if(result.result)
{
bestPattern = k;
break;
}
}
if(bestPattern != -1)
{
updateTile(tile, patterns[bestPattern], result.flip);
}
}
};
bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const
{
//TODO: this method should be virtual for river support
return map->getTile(pos).roadType != ERoadType::NO_ROAD;
}
void CDrawRoadsOperation::updateTile(TerrainTile & tile, const RoadPattern & pattern, const int flip)
{
//TODO: this method should be virtual for river support
const std::pair<int, int> & mapping = pattern.roadMapping;
tile.roadDir = gen->nextInt(mapping.first, mapping.second);
tile.extTileFlags = (tile.extTileFlags & 0xCF) | (flip << 4);
}
CDrawRoadsOperation::ValidationResult CDrawRoadsOperation::validateTile(const RoadPattern & pattern, const int3 & pos)
{
ValidationResult result(false);
if(!canApplyPattern(pattern))
return result;
for(int flip = 0; flip < 4; ++flip)
{
if((flip == FLIP_PATTERN_BOTH) && !(pattern.hasHFlip && pattern.hasVFlip))
continue;
if((flip == FLIP_PATTERN_HORIZONTAL) && !pattern.hasHFlip)
continue;
if((flip == FLIP_PATTERN_VERTICAL) && !(pattern.hasVFlip))
continue;
RoadPattern flipped = pattern;
flipPattern(flipped, flip);
bool validated = true;
for(int i = 0; i < 9; ++i)
{
if(4 == i)
continue;
int cx = pos.x + (i % 3) - 1;
int cy = pos.y + (i / 3) - 1;
int3 currentPos(cx, cy, pos.z);
bool hasSomething;
if(!map->isInTheMap(currentPos))
{
hasSomething = true; //road/river can go out of map
}
else
{
hasSomething = tileHasSomething(currentPos);
}
if(ruleIsSomething(flipped.data[i]))
{
if(!hasSomething)
{
validated = false;
break;
}
}
else if(ruleIsNone(flipped.data[i]))
{
if(hasSomething)
{
validated = false;
break;
}
}
else
{
assert(ruleIsAny(flipped.data[i]));
}
}
if(validated)
{
result.result = true;
result.flip = flip;
return result;
}
}
return result;
}

View File

@ -0,0 +1,58 @@
/*
* CDrawRoadsOperation.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 "../CRandomGenerator.h"
#include "CMap.h"
#include "CMapEditManager.h"
class CDrawRoadsOperation : public CMapOperation
{
public:
CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, ERoadType::ERoadType roadType, CRandomGenerator * gen);
void execute() override;
void undo() override;
void redo() override;
std::string getLabel() const override;
private:
struct RoadPattern
{
std::string data[9];
std::pair<int, int> roadMapping, riverMapping;
bool hasHFlip, hasVFlip;
};
struct ValidationResult
{
ValidationResult(bool result): result(result), flip(0){};
bool result;
int flip;
};
static const std::vector<RoadPattern> patterns;
void flipPattern(RoadPattern & pattern, int flip) const;
void updateTiles(std::set<int3> & invalidated);
ValidationResult validateTile(const RoadPattern & pattern, const int3 & pos);
void updateTile(TerrainTile & tile, const RoadPattern & pattern, const int flip);
bool canApplyPattern(const RoadPattern & pattern) const;
bool needUpdateTile(const TerrainTile & tile) const;
bool tileHasSomething(const int3 & pos) const;
CTerrainSelection terrainSel;
ERoadType::ERoadType roadType;
CRandomGenerator * gen;
};

View File

@ -261,22 +261,6 @@ public:
}
};
namespace ERiverType
{
enum ERiverType
{
NO_RIVER, CLEAR_RIVER, ICY_RIVER, MUDDY_RIVER, LAVA_RIVER
};
}
namespace ERoadType
{
enum ERoadType
{
NO_ROAD, DIRT_ROAD, GRAVEL_ROAD, COBBLESTONE_ROAD
};
}
/// The terrain tile describes the terrain type and the visual representation of the terrain.
/// Furthermore the struct defines whether the tile is visitable or/and blocked and which objects reside in it.
struct DLL_LINKAGE TerrainTile

View File

@ -6,6 +6,7 @@
#include "../mapObjects/CObjectClassesHandler.h"
#include "../mapObjects/CGHeroInstance.h"
#include "../VCMI_Lib.h"
#include "CDrawRoadsOperation.h"
MapRect::MapRect() : x(0), y(0), z(0), width(0), height(0)
{
@ -132,6 +133,18 @@ std::string CMapOperation::getLabel() const
return "";
}
MapRect CMapOperation::extendTileAround(const int3 & centerPos) const
{
return MapRect(int3(centerPos.x - 1, centerPos.y - 1, centerPos.z), 3, 3);
}
MapRect CMapOperation::extendTileAroundSafely(const int3 & centerPos) const
{
return extendTileAround(centerPos) & MapRect(int3(0, 0, centerPos.z), map->width, map->height);
}
CMapUndoManager::CMapUndoManager() : undoRedoLimit(10)
{
@ -226,6 +239,13 @@ void CMapEditManager::drawTerrain(ETerrainType terType, CRandomGenerator * gen/*
terrainSel.clearSelection();
}
void CMapEditManager::drawRoad(ERoadType::ERoadType roadType, CRandomGenerator* gen)
{
execute(make_unique<CDrawRoadsOperation>(map, terrainSel, roadType, gen ? gen : &(this->gen)));
terrainSel.clearSelection();
}
void CMapEditManager::insertObject(CGObjectInstance * obj, const int3 & pos)
{
execute(make_unique<CInsertObjectOperation>(map, obj, pos));
@ -960,16 +980,6 @@ CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const
return tiles;
}
MapRect CDrawTerrainOperation::extendTileAround(const int3 & centerPos) const
{
return MapRect(int3(centerPos.x - 1, centerPos.y - 1, centerPos.z), 3, 3);
}
MapRect CDrawTerrainOperation::extendTileAroundSafely(const int3 & centerPos) const
{
return extendTileAround(centerPos) & MapRect(int3(0, 0, centerPos.z), map->width, map->height);
}
CDrawTerrainOperation::ValidationResult::ValidationResult(bool result, const std::string & transitionReplacement /*= ""*/)
: result(result), transitionReplacement(transitionReplacement)
{

View File

@ -113,7 +113,14 @@ public:
virtual void redo() = 0;
virtual std::string getLabel() const = 0; /// Returns a display-able name of the operation.
protected:
protected:
MapRect extendTileAround(const int3 & centerPos) const;
MapRect extendTileAroundSafely(const int3 & centerPos) const; /// doesn't exceed map size
static const int FLIP_PATTERN_HORIZONTAL = 1;
static const int FLIP_PATTERN_VERTICAL = 2;
static const int FLIP_PATTERN_BOTH = 3;
CMap * map;
};
@ -161,7 +168,11 @@ public:
/// Draws terrain at the current terrain selection. The selection will be cleared automatically.
void drawTerrain(ETerrainType terType, CRandomGenerator * gen = nullptr);
void insertObject(CGObjectInstance * obj, const int3 & pos);
/// Draws roads at the current terrain selection. The selection will be cleared automatically.
void drawRoad(ERoadType::ERoadType roadType, CRandomGenerator * gen = nullptr);
void insertObject(CGObjectInstance * obj, const int3 & pos);
CTerrainSelection & getTerrainSelection();
CObjectSelection & getObjectSelection();
@ -326,8 +337,6 @@ private:
void updateTerrainTypes();
void invalidateTerrainViews(const int3 & centerPos);
InvalidTiles getInvalidTiles(const int3 & centerPos) const;
MapRect extendTileAround(const int3 & centerPos) const;
MapRect extendTileAroundSafely(const int3 & centerPos) const; /// doesn't exceed map size
void updateTerrainViews();
ETerrainGroup::ETerrainGroup getTerrainGroup(ETerrainType terType) const;
@ -339,10 +348,6 @@ private:
bool isSandType(ETerrainType terType) const;
void flipPattern(TerrainViewPattern & pattern, int flip) const;
static const int FLIP_PATTERN_HORIZONTAL = 1;
static const int FLIP_PATTERN_VERTICAL = 2;
static const int FLIP_PATTERN_BOTH = 3;
CTerrainSelection terrainSel;
ETerrainType terType;
CRandomGenerator * gen;

View File

@ -13,6 +13,8 @@
#include "CZonePlacer.h"
#include "../mapObjects/CObjectClassesHandler.h"
static const int3 dirs4[] = {int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0)};
void CMapGenerator::foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo)
{
for(const int3 &dir : dirs)
@ -23,6 +25,16 @@ void CMapGenerator::foreach_neighbour(const int3 &pos, std::function<void(int3&
}
}
void CMapGenerator::foreachDirectNeighbour(const int3& pos, std::function<void(int3& pos)> foo)
{
for(const int3 &dir : dirs4)
{
int3 n = pos + dir;
if(map->isInTheMap(n))
foo(n);
}
}
CMapGenerator::CMapGenerator() :
zonesTotal(0), monolithIndex(0)
@ -132,7 +144,7 @@ std::string CMapGenerator::getMapDescription() const
std::stringstream ss;
ss << boost::str(boost::format(std::string("Map created by the Random Map Generator.\nTemplate was %s, Random seed was %d, size %dx%d") +
", levels %s, humans %d, computers %d, water %s, monster %s, second expansion map") % mapGenOptions->getMapTemplate()->getName() %
", levels %s, humans %d, computers %d, water %s, monster %s, VCMI map") % mapGenOptions->getMapTemplate()->getName() %
randomSeed % map->width % map->height % (map->twoLevel ? "2" : "1") % static_cast<int>(mapGenOptions->getPlayerCount()) %
static_cast<int>(mapGenOptions->getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions->getWaterContent()] %
monsterStrengthStr[monsterStrengthIndex]);
@ -266,7 +278,49 @@ void CMapGenerator::fillZones()
createObstaclesCommon2();
//place actual obstacles matching zone terrain
for (auto it : zones)
{
it.second->createObstacles2(this);
}
#define PRINT_MAP_BEFORE_ROADS true
if (PRINT_MAP_BEFORE_ROADS) //enable to debug
{
std::ofstream out("road debug");
int levels = map->twoLevel ? 2 : 1;
int width = map->width;
int height = map->height;
for (int k = 0; k < levels; k++)
{
for (int j = 0; j<height; j++)
{
for (int i = 0; i<width; i++)
{
char t = '?';
switch (getTile(int3(i, j, k)).getTileType())
{
case ETileType::FREE:
t = ' '; break;
case ETileType::BLOCKED:
t = '#'; break;
case ETileType::POSSIBLE:
t = '-'; break;
case ETileType::USED:
t = 'O'; break;
}
out << t;
}
out << std::endl;
}
out << std::endl;
}
out << std::endl;
}
for (auto it : zones)
{
it.second->connectRoads(this); //draw roads after everything else has been placed
}
//find place for Grail
if (treasureZones.empty())
@ -421,8 +475,11 @@ void CMapGenerator::createConnections()
setOccupied (guardPos, ETileType::FREE); //just in case monster is too weak to spawn
zoneA->addMonster (this, guardPos, connection.getGuardStrength(), false, true);
//zones can make paths only in their own area
zoneA->crunchPath (this, guardPos, posA, zoneA->getId(), zoneA->getFreePaths()); //make connection towards our zone center
zoneB->crunchPath (this, guardPos, posB, zoneB->getId(), zoneB->getFreePaths()); //make connection towards other zone center
zoneA->crunchPath(this, guardPos, posA, zoneA->getFreePaths()); //make connection towards our zone center
zoneB->crunchPath(this, guardPos, posB, zoneB->getFreePaths()); //make connection towards other zone center
zoneA->addRoadNode(guardPos);
zoneB->addRoadNode(guardPos);
break; //we're done with this connection
}
}
@ -516,6 +573,13 @@ void CMapGenerator::addHeaderInfo()
addPlayerInfo();
}
void CMapGenerator::checkIsOnMap(const int3& tile) const
{
if (!map->isInTheMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
}
std::map<TRmgTemplateZoneId, CRmgTemplateZone*> CMapGenerator::getZones() const
{
return zones;
@ -523,67 +587,74 @@ std::map<TRmgTemplateZoneId, CRmgTemplateZone*> CMapGenerator::getZones() const
bool CMapGenerator::isBlocked(const int3 &tile) const
{
if (!map->isInTheMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isBlocked();
}
bool CMapGenerator::shouldBeBlocked(const int3 &tile) const
{
if (!map->isInTheMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].shouldBeBlocked();
}
bool CMapGenerator::isPossible(const int3 &tile) const
{
if (!map->isInTheMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isPossible();
}
bool CMapGenerator::isFree(const int3 &tile) const
{
if (!map->isInTheMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isFree();
}
bool CMapGenerator::isUsed(const int3 &tile) const
{
if (!map->isInTheMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isUsed();
}
bool CMapGenerator::isRoad(const int3& tile) const
{
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].isRoad();
}
void CMapGenerator::setOccupied(const int3 &tile, ETileType::ETileType state)
{
if (!map->isInTheMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
checkIsOnMap(tile);
tiles[tile.x][tile.y][tile.z].setOccupied(state);
}
CTileInfo CMapGenerator::getTile(const int3& tile) const
void CMapGenerator::setRoad(const int3& tile, ERoadType::ERoadType roadType)
{
if (!map->isInTheMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
checkIsOnMap(tile);
tiles[tile.x][tile.y][tile.z].setRoadType(roadType);
}
CTileInfo CMapGenerator::getTile(const int3& tile) const
{
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z];
}
void CMapGenerator::setNearestObjectDistance(int3 &tile, float value)
{
if (!map->isInTheMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
checkIsOnMap(tile);
tiles[tile.x][tile.y][tile.z].setNearestObjectDistance(value);
}
float CMapGenerator::getNearestObjectDistance(const int3 &tile) const
{
if (!map->isInTheMap(tile))
throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
checkIsOnMap(tile);
return tiles[tile.x][tile.y][tile.z].getNearestObjectDistance();
}

View File

@ -66,13 +66,18 @@ public:
void createConnections();
void findZonesForQuestArts();
void foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo);
void foreachDirectNeighbour(const int3 &pos, std::function<void(int3& pos)> foo);
bool isBlocked(const int3 &tile) const;
bool shouldBeBlocked(const int3 &tile) const;
bool isPossible(const int3 &tile) const;
bool isFree(const int3 &tile) const;
bool isUsed(const int3 &tile) const;
bool isRoad(const int3 &tile) const;
void setOccupied(const int3 &tile, ETileType::ETileType state);
void setRoad(const int3 &tile, ERoadType::ERoadType roadType);
CTileInfo getTile(const int3 & tile) const;
float getNearestObjectDistance(const int3 &tile) const;
@ -99,6 +104,7 @@ private:
//int questArtsRemaining;
int monolithIndex;
std::vector<ArtifactID> questArtifacts;
void checkIsOnMap(const int3 &tile) const; //throws
/// Generation methods
std::string getMapDescription() const;

View File

@ -33,6 +33,11 @@ CRmgTemplateZone::CTownInfo::CTownInfo() : townCount(0), castleCount(0), townDen
}
void CRmgTemplateZone::addRoadNode(const int3& node)
{
roadNodes.insert(node);
}
int CRmgTemplateZone::CTownInfo::getTownCount() const
{
return townCount;
@ -81,7 +86,7 @@ void CRmgTemplateZone::CTownInfo::setCastleDensity(int value)
castleDensity = value;
}
CTileInfo::CTileInfo():nearestObjectDistance(INT_MAX), terrain(ETerrainType::WRONG)
CTileInfo::CTileInfo():nearestObjectDistance(INT_MAX), terrain(ETerrainType::WRONG),roadType(ERoadType::NO_ROAD)
{
occupied = ETileType::POSSIBLE; //all tiles are initially possible to place objects or passages
}
@ -111,6 +116,12 @@ bool CTileInfo::isFree() const
{
return occupied == ETileType::FREE;
}
bool CTileInfo::isRoad() const
{
return roadType != ERoadType::NO_ROAD;
}
bool CTileInfo::isUsed() const
{
return occupied == ETileType::USED;
@ -135,6 +146,13 @@ void CTileInfo::setTerrainType(ETerrainType value)
terrain = value;
}
void CTileInfo::setRoadType(ERoadType::ERoadType value)
{
roadType = value;
// setOccupied(ETileType::FREE);
}
CRmgTemplateZone::CRmgTemplateZone() :
id(0),
type(ETemplateZoneType::PLAYER_START),
@ -517,11 +535,11 @@ void CRmgTemplateZone::fractalize(CMapGenerator* gen)
}
//connect with all the paths
crunchPath(gen, node, findClosestTile(freePaths, node), id, &freePaths);
crunchPath(gen, node, findClosestTile(freePaths, node), &freePaths);
//connect with nearby nodes
for (auto nearbyNode : nearbyNodes)
{
crunchPath(gen, node, nearbyNode, id, &freePaths);
crunchPath(gen, node, nearbyNode, &freePaths);
}
}
for (auto node : nodes)
@ -590,7 +608,7 @@ void CRmgTemplateZone::fractalize(CMapGenerator* gen)
//logGlobal->infoStream() << boost::format ("Zone %d subdivided fractally") %id;
}
bool CRmgTemplateZone::crunchPath (CMapGenerator* gen, const int3 &src, const int3 &dst, TRmgTemplateZoneId zone, std::set<int3>* clearedTiles)
bool CRmgTemplateZone::crunchPath(CMapGenerator* gen, const int3 &src, const int3 &dst, std::set<int3>* clearedTiles)
{
/*
make shortest path with free tiles, reachning dst or closest already free tile. Avoid blocks.
@ -611,7 +629,8 @@ do not leave zone border
}
auto lastDistance = distance;
gen->foreach_neighbour (currentPos, [this, gen, &currentPos, dst, &distance, &result, &end, clearedTiles](int3 &pos)
auto processNeighbours = [this, gen, &currentPos, dst, &distance, &result, &end, clearedTiles](int3 &pos)
{
if (!result) //not sure if lambda is worth it...
{
@ -643,15 +662,18 @@ do not leave zone border
}
}
}
});
};
gen->foreach_neighbour (currentPos,processNeighbours);
int3 anotherPos(-1, -1, -1);
if (!(result || distance < lastDistance)) //we do not advance, use more advaced pathfinding algorithm?
if (!(result || distance < lastDistance)) //we do not advance, use more advanced pathfinding algorithm?
{
//try any nearby tiles, even if its not closer than current
float lastDistance = 2 * distance; //start with significantly larger value
gen->foreach_neighbour(currentPos, [this, gen, &currentPos, dst, &lastDistance, &anotherPos, &end, clearedTiles](int3 &pos)
auto processNeighbours2 = [this, gen, &currentPos, dst, &lastDistance, &anotherPos, &end, clearedTiles](int3 &pos)
{
if (currentPos.dist2dSQ(dst) < lastDistance) //try closest tiles from all surrounding unused tiles
{
@ -666,7 +688,11 @@ do not leave zone border
}
}
}
});
};
gen->foreach_neighbour (currentPos,processNeighbours2);
if (anotherPos.valid())
{
if (clearedTiles)
@ -686,6 +712,82 @@ do not leave zone border
return result;
}
bool CRmgTemplateZone::createRoad(CMapGenerator* gen, const int3& src, const int3& dst)
{
//A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm
std::set<int3> closed; // The set of nodes already evaluated.
std::set<int3> open{src}; // The set of tentative nodes to be evaluated, initially containing the start node
std::map<int3, int3> cameFrom; // The map of navigated nodes.
std::map<int3, int> distances;
int3 currentNode = src;
cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition
distances[src] = 0;
// Cost from start along best known path.
// Estimated total cost from start to goal through y.
while (open.size())
{
int3 currentNode = *boost::min_element(open, [&distances](const int3 &pos1, const int3 &pos2) -> bool
{
return distances[pos1], distances[pos2];
});
vstd::erase_if_present(open, currentNode);
closed.insert(currentNode);
if (currentNode == dst || gen->isRoad(currentNode))
{
// The goal node was reached. Trace the path using
// the saved parent information and return path
int3 backTracking = currentNode;
while (cameFrom[backTracking].valid())
{
// add node to path
roads.insert(backTracking);
gen->setRoad(backTracking, ERoadType::COBBLESTONE_ROAD);
logGlobal->traceStream() << boost::format("Setting road at tile %s") % backTracking;
// do the same for the predecessor
backTracking = cameFrom[backTracking];
}
return true;
}
else
{
gen->foreach_neighbour(currentNode, [gen, this, &open, &closed, &cameFrom, &currentNode, &distances](int3& pos)
{
int distance = distances[currentNode] + 1;
int bestDistanceSoFar = 1e6; //FIXME: boost::limits
auto it = distances.find(pos);
if (it != distances.end())
bestDistanceSoFar = it->second;
if (distance < bestDistanceSoFar || !vstd::contains(closed, pos))
{
if (gen->map->checkForVisitableDir(currentNode, &gen->map->getTile(pos), pos))
//if (gen->isFree(pos))
{
if (vstd::contains(this->tileinfo, pos))
{
cameFrom[pos] = currentNode;
open.insert(pos);
distances[pos] = distance;
logGlobal->traceStream() << boost::format("Found connection between node %s and %s, current distance %d") % currentNode % pos % distance;
}
}
}
});
}
}
logGlobal->warnStream() << boost::format("Failed to create road from %s to %s") % src %dst;
return false;
}
void CRmgTemplateZone::addRequiredObject(CGObjectInstance * obj, si32 strength)
{
requiredObjects.push_back(std::make_pair(obj, strength));
@ -919,7 +1021,7 @@ bool CRmgTemplateZone::createTreasurePile(CMapGenerator* gen, int3 &pos, float m
gen->setOccupied(tile, ETileType::BLOCKED); //so that crunch path doesn't cut through objects
}
if (!crunchPath (gen, closestTile, closestFreeTile, id))
if (!crunchPath (gen, closestTile, closestFreeTile))
{
//we can't connect this pile, just block it off and start over
for (auto treasure : treasures)
@ -1246,6 +1348,7 @@ bool CRmgTemplateZone::placeMines (CMapGenerator* gen)
bool CRmgTemplateZone::createRequiredObjects(CMapGenerator* gen)
{
logGlobal->traceStream() << "Creating required objects";
for(const auto &obj : requiredObjects)
{
int3 pos;
@ -1254,11 +1357,11 @@ bool CRmgTemplateZone::createRequiredObjects(CMapGenerator* gen)
logGlobal->errorStream() << boost::format("Failed to fill zone %d due to lack of space") %id;
//TODO CLEANUP!
return false;
}
}
placeObject (gen, obj.first, pos);
guardObject (gen, obj.first, obj.second, (obj.first->ID == Obj::MONOLITH_TWO_WAY), true);
//paths to required objects constitute main paths of zone. otherwise they just may lead to middle and create dead zones
//paths to required objects constitute main paths of zone. otherwise they just may lead to middle and create dead zones
}
for (const auto &obj : closeObjects)
@ -1446,6 +1549,52 @@ void CRmgTemplateZone::createObstacles2(CMapGenerator* gen)
}
}
void CRmgTemplateZone::connectRoads(CMapGenerator* gen)
{
logGlobal->debug("Started building roads");
std::set<int3> processed;
while(!roadNodes.empty())
{
int3 node = *roadNodes.begin();
roadNodes.erase(node);
if(roads.empty())
{
//start road network
roads.insert(node);
logGlobal->debugStream() << "First node of road network: " << node;
}
else
{
auto comparator = [=](int3 lhs, int3 rhs) { return node.dist2dSQ(lhs) < node.dist2dSQ(rhs); };
int3 cross = * boost::range::min_element(processed, comparator);
logGlobal->debugStream() << "Building road from " << node << " to " << cross;
createRoad(gen, node, cross);
}
processed.insert(node);
}
drawRoads(gen);
logGlobal->debug("Finished building roads");
}
void CRmgTemplateZone::drawRoads(CMapGenerator* gen)
{
std::vector<int3> tiles;
for (auto tile : roads)
{
if(gen->map->isInTheMap(tile))
tiles.push_back (tile);
}
gen->editManager->getTerrainSelection().setSelection(tiles);
gen->editManager->drawRoad(ERoadType::COBBLESTONE_ROAD, &gen->rand);
}
bool CRmgTemplateZone::fill(CMapGenerator* gen)
{
initTerrainType(gen);
@ -1458,7 +1607,7 @@ bool CRmgTemplateZone::fill(CMapGenerator* gen)
placeMines(gen);
createRequiredObjects(gen);
createTreasures(gen);
logGlobal->infoStream() << boost::format ("Zone %d filled successfully") %id;
return true;
}
@ -1669,6 +1818,24 @@ void CRmgTemplateZone::placeObject(CMapGenerator* gen, CGObjectInstance* object,
auto artid = sh->quest->m5arts.front();
logGlobal->warnStream() << boost::format("Placed Seer Hut at %s, quest artifact %d is %s") % object->pos % artid % VLC->arth->artifacts[artid]->Name();
}
switch (object->ID)
{
case Obj::TOWN:
case Obj::RANDOM_TOWN:
case Obj::MONOLITH_TWO_WAY:
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
case Obj::MONOLITH_ONE_WAY_EXIT:
case Obj::SUBTERRANEAN_GATE:
{
roadNodes.insert(object->visitablePos());
}
break;
default:
break;
}
}
void CRmgTemplateZone::placeAndGuardObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos, si32 str, bool zoneGuard)
@ -1715,7 +1882,7 @@ bool CRmgTemplateZone::guardObject(CMapGenerator* gen, CGObjectInstance* object,
{
//crunching path may fail if center of the zone is directly over wide object
//make sure object is accessible before surrounding it with blocked tiles
if (crunchPath (gen, tile, findClosestTile(freePaths, tile), id, addToFreePaths ? &freePaths : nullptr))
if (crunchPath (gen, tile, findClosestTile(freePaths, tile), addToFreePaths ? &freePaths : nullptr))
{
guardTile = tile;
break;

View File

@ -47,15 +47,18 @@ public:
bool isPossible() const;
bool isFree() const;
bool isUsed() const;
bool isRoad() const;
void setOccupied(ETileType::ETileType value);
ETerrainType getTerrainType() const;
ETileType::ETileType getTileType() const;
void setTerrainType(ETerrainType value);
void setRoadType(ERoadType::ERoadType value);
private:
float nearestObjectDistance;
ETileType::ETileType occupied;
ETerrainType terrain;
ERoadType::ERoadType roadType;
};
class DLL_LINKAGE CTreasureInfo
@ -166,7 +169,8 @@ public:
void createTreasures(CMapGenerator* gen);
void createObstacles1(CMapGenerator* gen);
void createObstacles2(CMapGenerator* gen);
bool crunchPath (CMapGenerator* gen, const int3 &src, const int3 &dst, TRmgTemplateZoneId zone, std::set<int3>* clearedTiles = nullptr);
bool crunchPath(CMapGenerator* gen, const int3 &src, const int3 &dst, std::set<int3>* clearedTiles = nullptr);
std::vector<int3> getAccessibleOffsets (CMapGenerator* gen, CGObjectInstance* object);
void addConnection(TRmgTemplateZoneId otherZone);
@ -179,6 +183,8 @@ public:
ObjectInfo getRandomObject (CMapGenerator* gen, CTreasurePileInfo &info, ui32 desiredValue, ui32 maxValue, ui32 currentValue);
void placeAndGuardObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos, si32 str, bool zoneGuard = false);
void addRoadNode(const int3 & node);
void connectRoads(CMapGenerator * gen); //fills "roads" according to "roadNodes"
private:
//template info
@ -215,6 +221,12 @@ private:
std::set<int3> possibleTiles; //optimization purposes for treasure generation
std::vector<TRmgTemplateZoneId> connections; //list of adjacent zones
std::set<int3> freePaths; //core paths of free tiles that all other objects will be linked to
std::set<int3> roadNodes; //tiles to be connected with roads
std::set<int3> roads; //all tiles with roads
bool createRoad(CMapGenerator* gen, const int3 &src, const int3 &dst);
void drawRoads(CMapGenerator * gen); //actually updates tiles
bool pointIsIn(int x, int y);
void addAllPossibleObjects (CMapGenerator* gen); //add objects, including zone-specific, to possibleObjects

View File

@ -27,13 +27,13 @@
<Depends filename="lib/VCMI_lib.cbp" />
</Project>
<Project filename="test/Test.cbp">
<Depends filename="client/VCMI_client.cbp" />
<Depends filename="server/VCMI_server.cbp" />
<Depends filename="AI/EmptyAI/EmptyAI.cbp" />
<Depends filename="AI/VCAI/VCAI.cbp" />
<Depends filename="AI/StupidAI/StupidAI.cbp" />
<Depends filename="AI/BattleAI/BattleAI.cbp" />
<Depends filename="lib/VCMI_lib.cbp" />
<Depends filename="client/VCMI_client.cbp" />
</Project>
<Project filename="scripting/erm/ERM.cbp">
<Depends filename="lib/VCMI_lib.cbp" />