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

- Added basic mock/test generation - Added stub for terrain editing

This commit is contained in:
beegee1 2013-01-06 19:30:12 +00:00
parent d4c30667b8
commit 0311e5e6f5
28 changed files with 2096 additions and 239 deletions

View File

@ -94,6 +94,9 @@ typedef boost::int32_t si32; //signed int 32 bits (4 bytes)
typedef boost::int16_t si16; //signed int 16 bits (2 bytes)
typedef boost::int8_t si8; //signed int 8 bits (1 byte)
// Fixed width bool data type is important for serialization
static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
#if defined _M_X64 && defined _WIN32 //Win64 -> cannot load 32-bit DLLs for video handling
#define DISABLE_VIDEO
#endif

View File

@ -165,9 +165,17 @@ void updateStartInfo(std::string filename, StartInfo & sInfo, const CMapHeader *
PlayerSettings &pset = sInfo.playerInfos[i];
pset.color = i;
if(pinfo.canHumanPlay && namesIt != playerNames.cend())
{
setPlayer(pset, namesIt++->first, playerNames);
}
else
{
setPlayer(pset, 0, playerNames);
if(!pinfo.canHumanPlay)
{
pset.compOnly = true;
}
}
pset.castle = pinfo.defaultCastle();
pset.hero = pinfo.defaultHero();
@ -873,11 +881,6 @@ void CSelectionScreen::startScenario()
{
saveGameName = sInfo.mapname;
}
if(sInfo.createRandomMap)
{
// Random map generation fails for now, so don't start game...
return;
}
StartInfo * si = new StartInfo(sInfo);
CGP->removeFromGui();
@ -1138,9 +1141,9 @@ void SelectionTab::parseGames(const std::vector<ResourceID> &files, bool multi)
allItems.push_back(mapInfo);
}
catch(std::exception & e)
catch(const std::exception & e)
{
tlog3 << "Failed to process " << files[i].getName() <<": " << e.what() << std::endl;
tlog3 << "Error: Failed to process " << files[i].getName() <<": " << e.what() << std::endl;
}
}
}
@ -1590,8 +1593,8 @@ RandomMapTab::RandomMapTab()
// Map Size
mapSizeBtnGroup = new CHighlightableButtonsGroup(0);
mapSizeBtnGroup->pos.y = 81;
mapSizeBtnGroup->pos.x = 158;
mapSizeBtnGroup->pos.y += 81;
mapSizeBtnGroup->pos.x += 158;
const std::vector<std::string> mapSizeBtns = boost::assign::list_of("RANSIZS")("RANSIZM")("RANSIZL")("RANSIZX");
addButtonsToGroup(mapSizeBtnGroup, mapSizeBtns, 0, 3, 47, 198);
mapSizeBtnGroup->select(1, false);
@ -1621,8 +1624,8 @@ RandomMapTab::RandomMapTab()
const int BTNS_GROUP_LEFT_MARGIN = 67;
// Amount of players
playersCntGroup = new CHighlightableButtonsGroup(0);
playersCntGroup->pos.y = 153;
playersCntGroup->pos.x = BTNS_GROUP_LEFT_MARGIN;
playersCntGroup->pos.y += 153;
playersCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
addButtonsWithRandToGroup(playersCntGroup, numberDefs, 1, 8, NUMBERS_WIDTH, 204, 212);
playersCntGroup->onChange = [&](int btnId)
{
@ -1635,8 +1638,8 @@ RandomMapTab::RandomMapTab()
// Amount of teams
teamsCntGroup = new CHighlightableButtonsGroup(0);
teamsCntGroup->pos.y = 219;
teamsCntGroup->pos.x = BTNS_GROUP_LEFT_MARGIN;
teamsCntGroup->pos.y += 219;
teamsCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
addButtonsWithRandToGroup(teamsCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 214, 222);
teamsCntGroup->onChange = [&](int btnId)
{
@ -1646,8 +1649,8 @@ RandomMapTab::RandomMapTab()
// Computer only players
compOnlyPlayersCntGroup = new CHighlightableButtonsGroup(0);
compOnlyPlayersCntGroup->pos.y = 285;
compOnlyPlayersCntGroup->pos.x = BTNS_GROUP_LEFT_MARGIN;
compOnlyPlayersCntGroup->pos.y += 285;
compOnlyPlayersCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
addButtonsWithRandToGroup(compOnlyPlayersCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 224, 232);
compOnlyPlayersCntGroup->select(0, true);
compOnlyPlayersCntGroup->onChange = [&](int btnId)
@ -1660,8 +1663,8 @@ RandomMapTab::RandomMapTab()
// Computer only teams
compOnlyTeamsCntGroup = new CHighlightableButtonsGroup(0);
compOnlyTeamsCntGroup->pos.y = 351;
compOnlyTeamsCntGroup->pos.x = BTNS_GROUP_LEFT_MARGIN;
compOnlyTeamsCntGroup->pos.y += 351;
compOnlyTeamsCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
addButtonsWithRandToGroup(compOnlyTeamsCntGroup, numberDefs, 0, 6, NUMBERS_WIDTH, 234, 241);
deactivateButtonsFrom(compOnlyTeamsCntGroup, 0);
compOnlyTeamsCntGroup->onChange = [&](int btnId)
@ -1673,8 +1676,8 @@ RandomMapTab::RandomMapTab()
const int WIDE_BTN_WIDTH = 85;
// Water content
waterContentGroup = new CHighlightableButtonsGroup(0);
waterContentGroup->pos.y = 419;
waterContentGroup->pos.x = BTNS_GROUP_LEFT_MARGIN;
waterContentGroup->pos.y += 419;
waterContentGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
const std::vector<std::string> waterContentBtns = boost::assign::list_of("RANNONE")("RANNORM")("RANISLD");
addButtonsWithRandToGroup(waterContentGroup, waterContentBtns, 0, 2, WIDE_BTN_WIDTH, 243, 246);
waterContentGroup->onChange = [&](int btnId)
@ -1684,8 +1687,8 @@ RandomMapTab::RandomMapTab()
// Monster strength
monsterStrengthGroup = new CHighlightableButtonsGroup(0);
monsterStrengthGroup->pos.y = 485;
monsterStrengthGroup->pos.x = BTNS_GROUP_LEFT_MARGIN;
monsterStrengthGroup->pos.y += 485;
monsterStrengthGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
const std::vector<std::string> monsterStrengthBtns = boost::assign::list_of("RANWEAK")("RANNORM")("RANSTRG");
addButtonsWithRandToGroup(monsterStrengthGroup, monsterStrengthBtns, 0, 2, WIDE_BTN_WIDTH, 248, 251);
monsterStrengthGroup->onChange = [&](int btnId)

View File

@ -387,9 +387,9 @@ void CGuiHandler::run()
mainFPSmng->framerateDelay(); // holds a constant FPS
}
}
catch(const std::exception & ex)
catch(const std::exception & e)
{
tlog1 << "Error: " << ex.what() << std::endl;
tlog1 << "Error: " << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}

View File

@ -0,0 +1,428 @@
// Defines terrain view patterns.
// The following properties are mandatory:
// data: the 3x3 pattern
// mapping: maps the pattern to a range of terrain view images/frames of the .DEF, e.g. 10-15
// for patterns which represent two transitions a comma can be used to distinct between dirt and sand
// e.g. 10-15, 25-35 whereas the first value is always dirt and the second sand
// The following properties are optional:
// flipMode: should the same be flipped or different images be used(see rock) or is flip not supported at all; allowed values: sameImage | diffImages; default is: sameImage
// id: the identifier for the pattern if it's referenced by other patterns
// minPoints: the minimum points to reach to validate the pattern successfully
// The following table shows the rules for the 3x3 pattern of all terrain types:
// I) normal(e.g. grass, lava, ...):
// N: Native type
// D: Dirt border
// S: Sand border
// T: Sand OR dirt border(all Ts in the pattern are replaced by dirt OR sand)
// ?: T or N
// II) dirt:
// N: Native type
// D: Dirt border
// S: Sand border
// ?: Any border
// III) sand:
// N: Native type
// S: Sand border
// IV) water:
// N: Native type
// S: Sand border
// ?: Any border
// V) rock:
// N: Native type
// S: Sand border
// ?: Any border
// Some additional info:
// Rules can be combined with comma. e.g. T, N which would be the same meaning of ?. It's most useful in combination with pattern chaining.
// Chaining of patterns is supported. To reference a another pattern you simply add the <Ref Id> to the relevant field of the pattern.
// Rules can be given points: <[Rule OR Ref Id]-Points> With the property minPoints simple conditions can be built.
{
"normal" :
[
// Standard transitions
{
"data" :
[
"?", "?", "T",
"?", "N", "N",
"T", "N", "N"
],
"mapping" : "0-3, 20-23"
},
{
"data" :
[
"?", "N", "N",
"T", "N", "N",
"?", "N", "N"
],
"mapping" : "4-7, 24-27"
},
{
"data" :
[
"?", "T", "?",
"N", "N", "N",
"N", "N", "N"
],
"mapping" : "8-11, 28-31"
},
{
"data" :
[
"N", "N", "N",
"N", "N", "N",
"N", "N", "T"
],
"mapping" : "12-15, 32-35"
},
{
"data" :
[
"T", "T", "a-1,?",
"T", "N", "N",
"a-1,?", "N", "N"
],
"mapping" : "16-17, 36-37",
"id" : "a",
"minPoints" : 1
},
{
"data" :
[
"N", "N", "N",
"N", "N", "a-1,N",
"N", "a-1,N", "T"
],
"mapping" : "18-19, 38-39",
"minPoints" : 1
},
// Mixed transitions
{
"data" :
[
"T", "N", "N",
"N", "N", "N",
"N", "N", "T"
],
"mapping" : "40, 42"
},
{
"data" :
[
"D", "N", "N",
"N", "N", "N",
"N", "N", "S"
],
"mapping" : "41"
},
{
"data" :
[
"N", "N", "D,N",
"N", "N", "D",
"S", "D", "D,N"
],
"mapping" : "43"
},
{
"data" :
[
"N", "N", "S",
"N", "N", "D",
"D,N", "D", "D,N"
],
"mapping" : "44"
},
{
"data" :
[
"N", "N", "D,N",
"N", "N", "D",
"N", "N", "S"
],
"mapping" : "45"
},
{
"data" :
[
"N", "N", "N",
"N", "N", "N",
"D,N", "D", "S"
],
"mapping" : "46"
},
{
"data" :
[
"N", "N", "D,S,N",
"N", "N", "S",
"D", "D", "D,S,N"
],
"mapping" : "47"
},
{
"data" :
[
"N", "N", "D",
"N", "N", "D",
"D,S,N", "S", "D,S,N"
],
"mapping" : "48"
},
// No transition
{
"data" :
[
"N", "N", "N",
"N", "N", "N",
"N", "N", "N"
],
"mapping" : "49-72"
}
],
"dirt" :
[
// Standard transitions
{
"data" :
[
"?", "S", "S",
"S", "N", "N",
"S", "N", "N"
],
"mapping" : "0-3"
},
{
"data" :
[
"?", "D", "D",
"S", "N", "N",
"?", "D", "D"
],
"mapping" : "4-7"
},
{
"data" :
[
"?", "S", "?",
"D", "N", "D",
"D", "N", "D"
],
"mapping" : "8-11"
},
{
"data" :
[
"D", "D", "D",
"D", "N", "N",
"D", "N", "S"
],
"mapping" : "12-15"
},
{
"data" :
[
"S", "S", "D",
"S", "N", "b-1,D",
"D", "b-1,D", "D"
],
"mapping" : "16-17",
"id" : "a",
"minPoints" : 1
},
{
"data" :
[
"D", "D", "D",
"D", "N", "a-1,D",
"D", "a-1,D", "S"
],
"mapping" : "18-19",
"id" : "b",
"minPoints" : 1
},
// Mixed transition
{
"data" :
[
"S", "D", "D",
"D", "N", "D",
"D", "D", "S"
],
"mapping" : "20"
},
// No transition
{
"data" :
[
"D", "D", "D",
"D", "N", "D",
"D", "D", "D"
],
"mapping" : "21-44"
}
],
"sand" :
[
{
"data" :
[
"S", "S", "S",
"S", "N", "S",
"S", "S", "S"
],
"mapping" : "0-23"
}
],
"water" :
[
// Standard transitions
{
"data" :
[
"S", "S", "S",
"S", "N", "N",
"S", "N", "N"
],
"mapping" : "0-3"
},
{
"data" :
[
"?", "N", "N",
"S", "N", "N",
"?", "N", "N"
],
"mapping" : "4-7"
},
{
"data" :
[
"?", "S", "?",
"N", "N", "N",
"N", "N", "N"
],
"mapping" : "8-11"
},
{
"data" :
[
"N", "N", "N",
"N", "N", "N",
"N", "N", "S"
],
"mapping" : "12-15"
},
{
"data" :
[
"S", "S", "N",
"S", "N", "N",
"N", "N", "N"
],
"mapping" : "16-17",
"id" : "a"
},
{
"data" :
[
"N", "N", "N",
"N", "N", "a-1,N",
"N", "a-1,N", "S"
],
"mapping" : "18-19",
"minPoints" : 1
},
// No transition
{
"data" :
[
"N", "N", "N",
"N", "N", "N",
"N", "N", "N"
],
"mapping" : "20-32"
}
],
"rock" :
[
// No transition
{
"data" :
[
"N", "N", "N",
"N", "N", "N",
"N", "N", "N"
],
"mapping" : "0-7"
},
// Standard transitions
{
"data" :
[
"?", "S", "?",
"S", "N", "N",
"?", "N", "N"
],
"mapping" : "8-15",
"flipMode" : "diffImages"
},
{
"data" :
[
"?", "N", "N",
"S", "N", "N",
"?", "N", "N"
],
"mapping" : "16-19",
"flipMode" : "diffImages"
},
{
"data" :
[
"?", "S", "?",
"N", "N", "N",
"N", "N", "N"
],
"mapping" : "20-23",
"flipMode" : "diffImages"
},
{
"data" :
[
"N", "N", "N",
"N", "N", "N",
"N", "N", "S"
],
"mapping" : "24-31",
"flipMode" : "diffImages"
},
{
"data" :
[
"S", "S", "N",
"S", "N", "N",
"N", "N", "N"
],
"mapping" : "32-39",
"flipMode" : "diffImages",
"id" : "a"
},
{
"data" :
[
"N", "N", "N",
"N", "N", "a-1,N",
"N", "a-1,N", "S"
],
"mapping" : "40-47",
"flipMode" : "diffImages",
"minPoints" : 1
}
]
}

View File

@ -9,6 +9,7 @@
#include "CSpellHandler.h"
#include "CObjectHandler.h"
#include "NetPacks.h"
#include "GameConstants.h"
using namespace boost::assign;
@ -776,14 +777,23 @@ void CArtHandler::initAllowedArtifactsList(const std::vector<ui8> &allowed)
allowedArtifacts.push_back(artifacts[i]);
else //check if active modules allow artifact to be every used
{
if (artifacts[i]->possibleSlots[ArtBearer::COMMANDER].size() && VLC->modh->modules.COMMANDERS ||
artifacts[i]->possibleSlots[ArtBearer::CREATURE].size() && VLC->modh->modules.STACK_ARTIFACT)
if ((artifacts[i]->possibleSlots[ArtBearer::COMMANDER].size() && VLC->modh->modules.COMMANDERS) ||
(artifacts[i]->possibleSlots[ArtBearer::CREATURE].size() && VLC->modh->modules.STACK_ARTIFACT))
allowedArtifacts.push_back(artifacts[i]);
//keep im mind that artifact can be worn by more than one type of bearer
}
}
}
std::vector<ui8> CArtHandler::getDefaultAllowedArtifacts() const
{
std::vector<ui8> allowedArtifacts;
allowedArtifacts.resize(127, 1);
allowedArtifacts.resize(141, 0);
allowedArtifacts.resize(GameConstants::ARTIFACTS_QUANTITY, 1);
return allowedArtifacts;
}
CArtifactInstance::CArtifactInstance()
{
init();

View File

@ -256,6 +256,13 @@ public:
CArtHandler();
~CArtHandler();
/**
* Gets a list of default allowed artifacts.
*
* @return a list of allowed artifacts, the index is the artifact id and the value either 0 for not allowed or 1 for allowed
*/
std::vector<ui8> getDefaultAllowedArtifacts() const;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & artifacts & allowedArtifacts & treasures & minors & majors & relics

View File

@ -26,6 +26,7 @@
#include "Filesystem/CResourceLoader.h"
#include "GameConstants.h"
#include "RMG/CMapGenOptions.h"
#include "RMG/CMapGenerator.h"
DLL_LINKAGE boost::rand48 ran;
class CGObjectInstance;
@ -872,7 +873,46 @@ void CGameState::init(StartInfo * si)
if(scenarioOps->createRandomMap)
{
tlog0 << "Create random map." << std::endl;
//TODO random map
// Create player settings for RMG
std::map<TPlayerColor, CMapGenerator::CPlayerSettings> players;
BOOST_FOREACH(auto pInfo, scenarioOps->playerInfos)
{
const PlayerSettings & startSettings = pInfo.second;
CMapGenerator::CPlayerSettings player;
player.setColor(startSettings.color);
player.setStartingTown(startSettings.castle);
if(startSettings.playerID > 0)
{
player.setPlayerType(CMapGenerator::CPlayerSettings::HUMAN);
}
else if(startSettings.compOnly)
{
player.setPlayerType(CMapGenerator::CPlayerSettings::COMP_ONLY);
}
players[player.getColor()] = player;
}
// Gen map
CMapGenerator mapGen(*scenarioOps->mapGenOptions, players, scenarioOps->seedToBeUsed);
map = mapGen.generate().release();
// Update starting options
for(auto it = scenarioOps->playerInfos.begin(); it != scenarioOps->playerInfos.end();)
{
PlayerSettings & pSettings = it->second;
if(!(map->players[pSettings.color].canHumanPlay || map->players[pSettings.color].canComputerPlay))
{
scenarioOps->playerInfos.erase(it++);
}
else
{
pSettings.compOnly = !(map->players[pSettings.color].canHumanPlay);
pSettings.team = map->players[pSettings.color].team;
pSettings.castle = map->players[pSettings.color].defaultCastle();
++it;
}
}
}
else
{
@ -902,7 +942,6 @@ void CGameState::init(StartInfo * si)
VLC->arth->initAllowedArtifactsList(map->allowedArtifact);
tlog0 << "Map loaded!" << std::endl;
//tlog0 <<"Reading and detecting map file (together): "<<tmh.getDif()<<std::endl;
tlog0 << "\tOur checksum for the map: "<< map->checksum << std::endl;
if(scenarioOps->mapfileChecksum)

View File

@ -498,3 +498,10 @@ std::vector<ui8> CHeroHandler::getDefaultAllowedHeroes() const
return allowedHeroes;
}
std::vector<ui8> CHeroHandler::getDefaultAllowedAbilities() const
{
std::vector<ui8> allowedAbilities;
allowedAbilities.resize(GameConstants::SKILL_QUANTITY, 1);
return allowedAbilities;
}

View File

@ -218,10 +218,17 @@ public:
* create a JSON config file or merge it with a existing config file which describes which heroes can be used for
* random map generation / map editor(default map settings). (Gelu, ... should be excluded)
*
* @return a list of allowed heroes, the index is the hero id and the value either 0 for not allowed and 1 for allowed
* @return a list of allowed heroes, the index is the hero id and the value either 0 for not allowed or 1 for allowed
*/
std::vector<ui8> getDefaultAllowedHeroes() const;
/**
* Gets a list of default allowed abilities. OH3 abilities/skills are all allowed by default.
*
* @return a list of allowed abilities, the index is the ability id and the value either 0 for not allowed or 1 for allowed
*/
std::vector<ui8> getDefaultAllowedAbilities() const;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & classes & heroes & expPerLevel & ballistics & terrCosts;

View File

@ -5,48 +5,50 @@ include_directories(${CMAKE_HOME_DIRECTORY} ${CMAKE_CURRENT_SOURCE_DIRECTORY} ${
include_directories(${Boost_INCLUDE_DIRS} ${SDL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR})
set(lib_SRCS
Filesystem/CBinaryReader.cpp
Filesystem/CFilesystemLoader.cpp
Filesystem/CMemoryStream.cpp
Filesystem/CFileInfo.cpp
Filesystem/CLodArchiveLoader.cpp
Filesystem/CResourceLoader.cpp
Filesystem/CFileInputStream.cpp
Filesystem/CCompressedStream.cpp
Mapping/CCampaignHandler.cpp
Mapping/CMap.cpp
Mapping/CMapInfo.cpp
Mapping/CMapService.cpp
RMG/CMapGenOptions.cpp
BattleAction.cpp
BattleHex.cpp
BattleState.cpp
CArtHandler.cpp
CBattleCallback.cpp
CBuildingHandler.cpp
CConfigHandler.cpp
CConsoleHandler.cpp
CCreatureHandler.cpp
CCreatureSet.cpp
CDefObjInfoHandler.cpp
CGameInterface.cpp
CGameState.cpp
CGeneralTextHandler.cpp
CHeroHandler.cpp
CLogger.cpp
CModHandler.cpp
CObjectHandler.cpp
CObstacleInstance.cpp
Connection.cpp
CSpellHandler.cpp
CThreadHelper.cpp
CTownHandler.cpp
HeroBonus.cpp
IGameCallback.cpp
JsonNode.cpp
NetPacksLib.cpp
ResourceSet.cpp
VCMI_Lib.cpp
Filesystem/CBinaryReader.cpp
Filesystem/CFilesystemLoader.cpp
Filesystem/CMemoryStream.cpp
Filesystem/CFileInfo.cpp
Filesystem/CLodArchiveLoader.cpp
Filesystem/CResourceLoader.cpp
Filesystem/CFileInputStream.cpp
Filesystem/CCompressedStream.cpp
Mapping/CCampaignHandler.cpp
Mapping/CMap.cpp
Mapping/CMapEditManager.cpp
Mapping/CMapInfo.cpp
Mapping/CMapService.cpp
RMG/CMapGenOptions.cpp
RMG/CMapGenerator.cpp
BattleAction.cpp
BattleHex.cpp
BattleState.cpp
CArtHandler.cpp
CBattleCallback.cpp
CBuildingHandler.cpp
CConfigHandler.cpp
CConsoleHandler.cpp
CCreatureHandler.cpp
CCreatureSet.cpp
CDefObjInfoHandler.cpp
CGameInterface.cpp
CGameState.cpp
CGeneralTextHandler.cpp
CHeroHandler.cpp
CLogger.cpp
CModHandler.cpp
CObjectHandler.cpp
CObstacleInstance.cpp
Connection.cpp
CSpellHandler.cpp
CThreadHelper.cpp
CTownHandler.cpp
HeroBonus.cpp
IGameCallback.cpp
JsonNode.cpp
NetPacksLib.cpp
ResourceSet.cpp
VCMI_Lib.cpp
)
set(lib_HEADERS
@ -55,6 +57,7 @@ set(lib_HEADERS
AI_Base.h
CondSh.h
ConstTransitivePtr.h
CRandomGenerator.h
CScriptingModule.h
CStopWatch.h
GameConstants.h

View File

@ -1971,11 +1971,9 @@ bool CGTownInstance::hasCapitol() const
return hasBuilt(EBuilding::CAPITOL);
}
CGTownInstance::CGTownInstance()
:IShipyard(this), IMarket(this)
:IShipyard(this), IMarket(this), town(nullptr), builded(0), destroyed(0), identifier(0), alignment(0xff)
{
builded=-1;
destroyed=-1;
town=NULL;
}
CGTownInstance::~CGTownInstance()

122
lib/CRandomGenerator.h Normal file
View File

@ -0,0 +1,122 @@
/*
* CRandomGenerator.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 <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include <boost/random/variate_generator.hpp>
typedef boost::mt19937 TGenerator;
typedef boost::random::uniform_int_distribution<int> TIntDist;
typedef boost::random::uniform_real_distribution<double> TRealDist;
typedef boost::variate_generator<TGenerator &, TIntDist> TRandI;
typedef boost::variate_generator<TGenerator &, TRealDist> TRand;
/**
* The random generator randomly generates integers and real numbers("doubles") between
* a given range. This is a header only class and mainly a wrapper for
* convenient usage of the boost random API.
*/
class CRandomGenerator
{
public:
/**
* Constructor. Seeds the generator with the current time by default.
*/
CRandomGenerator()
{
gen.seed(std::time(nullptr));
}
/**
* Seeds the generator with the given value.
*
* @param value the random seed
*/
void seed(int value)
{
gen.seed(value);
}
/**
* Gets a generator which generates integers in the given range.
*
* Example how to use:
* @code
* TRandI rand = getRangeI(0, 10);
* int a = rand(); // with the operator() the next value can be obtained
* int b = rand(); // you can generate more values
* @endcode
*
* @param lower the lower boundary
* @param upper the upper boundary
* @return the generator which can be used to generate integer numbers
*/
TRandI getRangeI(int lower, int upper)
{
TIntDist range(lower, upper);
return TRandI(gen, range);
}
/**
* Gets a integer in the given range. In comparison to getRangeI it's
* a convenient method if you want to generate only one value in a given
* range.
*
* @param lower the lower boundary
* @param upper the upper boundary
* @return the generated integer
*/
int getInteger(int lower, int upper)
{
return getRangeI(lower, upper)();
}
/**
* Gets a generator which generates doubles in the given range.
*
* Example how to use:
* @code
* TRand rand = getRange(23.56, 32.10);
* double a = rand(); // with the operator() the next value can be obtained
* double b = rand(); // you can generate more values
* @endcode
*
* @param lower the lower boundary
* @param upper the upper boundary
* @return the generated double
*/
TRand getRange(double lower, double upper)
{
TRealDist range(lower, upper);
return TRand(gen, range);
}
/**
* Gets a double in the given range. In comparison to getRange it's
* a convenient method if you want to generate only one value in a given
* range.
*
* @param lower the lower boundary
* @param upper the upper boundary
* @return the generated double
*/
double getDouble(double lower, double upper)
{
return getRange(lower, upper)();
}
private:
/** The actual boost random generator. */
TGenerator gen;
};

View File

@ -378,3 +378,10 @@ void CSpellHandler::loadSpells()
risingSpells += 38, 39, 40;
mindSpells += 50, 59, 60, 61, 62;
}
std::vector<ui8> CSpellHandler::getDefaultAllowedSpells() const
{
std::vector<ui8> allowedSpells;
allowedSpells.resize(GameConstants::SPELLS_QUANTITY, 1);
return allowedSpells;
}

View File

@ -101,6 +101,13 @@ public:
std::set<TSpell> mindSpells;
void loadSpells();
/**
* Gets a list of default allowed spells. OH3 spells are all allowed by default.
*
* @return a list of allowed spells, the index is the spell id and the value either 0 for not allowed or 1 for allowed
*/
std::vector<ui8> getDefaultAllowedSpells() const;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & spells & damageSpells & risingSpells & mindSpells;

View File

@ -6,6 +6,7 @@
#include "../CTownHandler.h"
#include "../CHeroHandler.h"
#include "../CDefObjInfoHandler.h"
#include "../CSpellHandler.h"
SHeroName::SHeroName() : heroId(-1)
{
@ -13,8 +14,8 @@ SHeroName::SHeroName() : heroId(-1)
}
PlayerInfo::PlayerInfo(): canHumanPlay(false), canComputerPlay(false),
aiTactic(EAiTactic::RANDOM), isFactionRandom(false), mainHeroPortrait(-1), hasMainTown(true),
generateHeroAtMainTown(true), team(255), generateHero(false), p7(0), hasHero(false), customHeroID(-1), powerPlaceholders(-1)
aiTactic(EAiTactic::RANDOM), isFactionRandom(false), mainHeroPortrait(-1), hasMainTown(false),
generateHeroAtMainTown(false), team(255), generateHero(false), p7(0), hasHero(false), customHeroID(-1), powerPlaceholders(-1)
{
allowedFactions = VLC->townh->getDefaultAllowedFactions();
}
@ -129,6 +130,7 @@ CMapHeader::CMapHeader() : version(EMapFormat::SOD), height(72), width(72),
twoLevel(true), difficulty(1), levelLimit(0), howManyTeams(0), areAnyPlayers(false)
{
allowedHeroes = VLC->heroh->getDefaultAllowedHeroes();
players.resize(GameConstants::PLAYER_LIMIT);
}
CMapHeader::~CMapHeader()
@ -138,7 +140,9 @@ CMapHeader::~CMapHeader()
CMap::CMap() : checksum(0), terrain(nullptr), grailRadious(0)
{
allowedAbilities = VLC->heroh->getDefaultAllowedAbilities();
allowedArtifact = VLC->arth->getDefaultAllowedArtifacts();
allowedSpell = VLC->spellh->getDefaultAllowedSpells();
}
CMap::~CMap()
@ -186,6 +190,7 @@ void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
}
}
}
void CMap::addBlockVisTiles(CGObjectInstance * obj)
{
for(int fx=0; fx<8; ++fx)
@ -286,3 +291,23 @@ void CMap::eraseArtifactInstance(CArtifactInstance * art)
assert(artInstances[art->id] == art);
artInstances[art->id].dellNull();
}
void CMap::addQuest(CGObjectInstance * quest)
{
auto q = dynamic_cast<IQuestObject *>(quest);
q->quest->qid = quests.size();
quests.push_back(q->quest);
}
void CMap::initTerrain()
{
terrain = new TerrainTile**[width];
for(int i = 0; i < width; ++i)
{
terrain[i] = new TerrainTile*[height];
for(int j = 0; j < height; ++j)
{
terrain[i][j] = new TerrainTile[twoLevel ? 2 : 1];
}
}
}

View File

@ -116,10 +116,10 @@ struct DLL_LINKAGE PlayerInfo
/** The list of renamed heroes. */
std::vector<SHeroName> heroesNames;
/** True if the player has a main town. The default value is true. */
/** True if the player has a main town. The default value is false. */
bool hasMainTown;
/** True if the main hero should be generated at the main town. The default value is true. */
/** True if the main hero should be generated at the main town. The default value is false. */
bool generateHeroAtMainTown;
/** The position of the main town. */
@ -286,11 +286,9 @@ struct DLL_LINKAGE DisposedHero
}
};
/// Class which manages map events.
/**
* The map event is an event which gives or takes resources for a specific
* amount of players and can appear regularly or once a time.
* The map event is an event which e.g. gives or takes resources of a specific
* amount to/from players and can appear regularly or once a time.
*/
class DLL_LINKAGE CMapEvent
{
@ -584,7 +582,7 @@ public:
/** Specifies the victory condition. The default value is defeat all enemies. */
VictoryCondition victoryCondition;
/** A list containing information about players. */
/** A list containing information about players. The default size of the vector is GameConstants::PLAYER_LIMIT. */
std::vector<PlayerInfo> players;
/** The number of teams. */
@ -714,10 +712,22 @@ public:
*/
void addNewArtifactInstance(CArtifactInstance * art);
/**
* Adds the specified quest instance to the list of quests.
*
* @param quest the quest object which should be added to the list of quests
*/
void addQuest(CGObjectInstance * quest);
/**
* Initializes the terrain of the map by allocating memory.
*/
void initTerrain();
/** the checksum of the map */
ui32 checksum;
/** a 3-dimensional array of terrain tiles, access is as follows: x, y, level */
/** a 3-dimensional array of terrain tiles, access is as follows: x, y, level. where level=1 is underground */
TerrainTile*** terrain;
/** list of rumors */

View File

@ -0,0 +1,386 @@
#include "StdInc.h"
#include "CMapEditManager.h"
#include "../JsonNode.h"
#include "../Filesystem/CResourceLoader.h"
#include "../CDefObjInfoHandler.h"
const std::string TerrainViewPattern::FLIP_MODE_SAME_IMAGE = "sameImage";
const std::string TerrainViewPattern::FLIP_MODE_DIFF_IMAGES = "diffImages";
const std::string TerrainViewPattern::RULE_DIRT = "D";
const std::string TerrainViewPattern::RULE_SAND = "S";
const std::string TerrainViewPattern::RULE_TRANSITION = "T";
const std::string TerrainViewPattern::RULE_NATIVE = "N";
const std::string TerrainViewPattern::RULE_ANY = "?";
TerrainViewPattern::TerrainViewPattern() : minPoints(0), flipMode(FLIP_MODE_SAME_IMAGE),
terGroup(ETerrainGroup::NORMAL)
{
}
CTerrainViewPatternConfig::CTerrainViewPatternConfig()
{
const JsonNode config(ResourceID("config/terrainViewPatterns.json"));
const std::map<std::string, ETerrainGroup::ETerrainGroup> terGroups
= boost::assign::map_list_of("normal", ETerrainGroup::NORMAL)("dirt", ETerrainGroup::DIRT)
("sand", ETerrainGroup::SAND)("water", ETerrainGroup::WATER)("rock", ETerrainGroup::ROCK);
BOOST_FOREACH(auto terMapping, terGroups)
{
BOOST_FOREACH(const JsonNode & ptrnNode, config[terMapping.first].Vector())
{
TerrainViewPattern pattern;
// Read pattern data
const JsonVector & data = ptrnNode["data"].Vector();
if(data.size() != 9)
{
throw std::runtime_error("Size of pattern's data vector has to be 9.");
}
for(int i = 0; i < data.size(); ++i)
{
std::string cell = data[i].String();
boost::algorithm::erase_all(cell, " ");
std::vector<std::string> rules;
boost::split(rules, cell, boost::is_any_of(","));
BOOST_FOREACH(std::string ruleStr, rules)
{
std::vector<std::string> rule;
boost::split(rule, ruleStr, boost::is_any_of("-"));
std::pair<std::string, int> pair;
pair.first = rule[0];
if(rule.size() > 1)
{
pair.second = boost::lexical_cast<int>(rule[1]);
}
else
{
pair.second = 0;
}
pattern.data[i].push_back(pair);
}
}
// Read mapping
std::string mappingStr = ptrnNode["mapping"].String();
boost::algorithm::erase_all(mappingStr, " ");
std::vector<std::string> mappings;
boost::split(mappings, mappingStr, boost::is_any_of(","));
BOOST_FOREACH(std::string mapping, mappings)
{
std::vector<std::string> range;
boost::split(range, mapping, boost::is_any_of("-"));
pattern.mapping.push_back(std::make_pair(boost::lexical_cast<int>(range[0]),
boost::lexical_cast<int>(range.size() > 1 ? range[1] : range[0])));
}
// Read optional attributes
pattern.id = ptrnNode["id"].String();
pattern.minPoints = static_cast<int>(ptrnNode["minPoints"].Float());
pattern.flipMode = ptrnNode["flipMode"].String();
if(pattern.flipMode.empty())
{
pattern.flipMode = TerrainViewPattern::FLIP_MODE_SAME_IMAGE;
}
pattern.terGroup = terMapping.second;
patterns[terMapping.second].push_back(pattern);
}
}
}
const std::vector<TerrainViewPattern> & CTerrainViewPatternConfig::getPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const
{
return patterns.find(terGroup)->second;
}
CMapEditManager::CMapEditManager(const CTerrainViewPatternConfig * terViewPatternConfig, CMap * map, int randomSeed /*= std::time(nullptr)*/)
: map(map), terViewPatternConfig(terViewPatternConfig)
{
gen.seed(randomSeed);
}
void CMapEditManager::clearTerrain()
{
for(int i = 0; i < map->width; ++i)
{
for(int j = 0; j < map->height; ++j)
{
map->terrain[i][j][0].terType = ETerrainType::WATER;
map->terrain[i][j][0].terView = gen.getInteger(20, 32);
if(map->twoLevel)
{
map->terrain[i][j][1].terType = ETerrainType::ROCK;
map->terrain[i][j][1].terView = 0;
}
}
}
}
void CMapEditManager::drawTerrain(ETerrainType::ETerrainType terType, int posx, int posy, int width, int height, bool underground)
{
bool mapLevel = underground ? 1 : 0;
for(int i = posx; i < posx + width; ++i)
{
for(int j = posy; j < posy + height; ++j)
{
map->terrain[i][j][mapLevel].terType = terType;
}
}
//TODO there are situations where more tiles are affected implicitely
//TODO add coastal bit to extTileFlags appropriately
//updateTerrainViews(posx - 1, posy - 1, width + 2, height + 2, mapLevel);
}
void CMapEditManager::updateTerrainViews(int posx, int posy, int width, int height, int mapLevel)
{
for(int i = posx; i < posx + width; ++i)
{
for(int j = posy; j < posy + height; ++j)
{
const std::vector<TerrainViewPattern> & patterns =
terViewPatternConfig->getPatternsForGroup(getTerrainGroup(map->terrain[i][j][mapLevel].terType));
// Detect a pattern which fits best
int totalPoints, bestPattern, bestFlip = -1;
std::string transitionReplacement;
for(int i = 0; i < patterns.size(); ++i)
{
const TerrainViewPattern & pattern = patterns[i];
for(int flip = 0; flip < 3; ++flip)
{
ValidationResult valRslt = validateTerrainView(i, j, mapLevel, flip > 0 ? getFlippedPattern(pattern, flip) : pattern);
if(valRslt.result)
{
if(valRslt.points > totalPoints)
{
totalPoints = valRslt.points;
bestPattern = i;
bestFlip = flip;
transitionReplacement = valRslt.transitionReplacement;
}
break;
}
}
}
if(bestPattern == -1)
{
continue;
}
// Get mapping
const TerrainViewPattern & pattern = patterns[bestPattern];
std::pair<int, int> mapping;
if(transitionReplacement.empty())
{
mapping = pattern.mapping[0];
}
else
{
mapping = transitionReplacement == TerrainViewPattern::RULE_DIRT ? pattern.mapping[0] : pattern.mapping[1];
}
// Set terrain view
if(pattern.flipMode == TerrainViewPattern::FLIP_MODE_SAME_IMAGE)
{
map->terrain[i][j][mapLevel].terView = gen.getInteger(mapping.first, mapping.second);
map->terrain[i][j][mapLevel].extTileFlags = bestFlip;
}
else
{
int range = (mapping.second - mapping.first) / 4;
map->terrain[i][j][mapLevel].terView = gen.getInteger(mapping.first + bestFlip * range,
mapping.first + (bestFlip + 1) * range - 1);
map->terrain[i][j][mapLevel].extTileFlags = 0;
}
}
}
}
ETerrainGroup::ETerrainGroup CMapEditManager::getTerrainGroup(ETerrainType::ETerrainType terType) const
{
switch(terType)
{
case ETerrainType::DIRT:
return ETerrainGroup::DIRT;
case ETerrainType::SAND:
return ETerrainGroup::SAND;
case ETerrainType::WATER:
return ETerrainGroup::WATER;
case ETerrainType::ROCK:
return ETerrainGroup::ROCK;
default:
return ETerrainGroup::NORMAL;
}
}
CMapEditManager::ValidationResult CMapEditManager::validateTerrainView(int posx, int posy, int mapLevel, const TerrainViewPattern & pattern) const
{
ETerrainType::ETerrainType centerTerType = map->terrain[posx][posy][mapLevel].terType;
int totalPoints = 0;
std::string transitionReplacement;
for(int i = 0; i < 9; ++i)
{
// The center, middle cell can be skipped
if(i == 4)
{
continue;
}
// Get terrain group of the current cell
int cx = posx + (i % 3) - 1;
int cy = posy + (i / 3) - 1;
bool isAlien = false;
ETerrainType::ETerrainType terType;
if(cx < 0 || cx >= map->width || cy < 0 || cy >= map->height)
{
terType = centerTerType;
}
else
{
terType = map->terrain[cx][cy][mapLevel].terType;
if(terType != centerTerType)
{
isAlien = true;
}
}
// Validate all rules per cell
int topPoints = -1;
for(int j = 0; j < pattern.data[i].size(); ++j)
{
const std::pair<std::string, int> & rulePair = pattern.data[i][j];
const std::string & rule = rulePair.first;
bool isNative = (rule == TerrainViewPattern::RULE_NATIVE || rule == TerrainViewPattern::RULE_ANY) && !isAlien;
auto validationRslt = [&](bool rslt)
{
if(rslt)
{
topPoints = std::max(topPoints, rulePair.second);
}
return rslt;
};
// Validate cell with the ruleset of the pattern
bool validation;
if(pattern.terGroup == ETerrainGroup::NORMAL)
{
bool isDirt = (rule == TerrainViewPattern::RULE_DIRT
|| rule == TerrainViewPattern::RULE_TRANSITION || rule == TerrainViewPattern::RULE_ANY)
&& isAlien && !isSandType(terType);
bool isSand = (rule == TerrainViewPattern::RULE_SAND || rule == TerrainViewPattern::RULE_TRANSITION
|| rule == TerrainViewPattern::RULE_ANY)
&& isSandType(terType);
if(transitionReplacement.empty() && (rule == TerrainViewPattern::RULE_TRANSITION
|| rule == TerrainViewPattern::RULE_ANY) && (isDirt || isSand))
{
transitionReplacement = isDirt ? TerrainViewPattern::RULE_DIRT : TerrainViewPattern::RULE_SAND;
}
validation = validationRslt((isDirt && transitionReplacement != TerrainViewPattern::RULE_SAND)
|| (isSand && transitionReplacement != TerrainViewPattern::RULE_DIRT)
|| isNative);
}
else if(pattern.terGroup == ETerrainGroup::DIRT)
{
bool isSand = rule == TerrainViewPattern::RULE_SAND && isSandType(terType);
bool isDirt = rule == TerrainViewPattern::RULE_DIRT && !isSandType(terType) && !isNative;
validation = validationRslt(rule == TerrainViewPattern::RULE_ANY || isSand || isDirt || isNative);
}
else if(pattern.terGroup == ETerrainGroup::SAND || pattern.terGroup == ETerrainGroup::WATER ||
pattern.terGroup == ETerrainGroup::ROCK)
{
bool isSand = rule == TerrainViewPattern::RULE_SAND && isSandType(terType) && !isNative;
validation = validationRslt(rule == TerrainViewPattern::RULE_ANY || isSand || isNative);
}
if(!validation)
{
return ValidationResult(false);
}
}
if(topPoints == -1)
{
return ValidationResult(false);
}
else
{
totalPoints += topPoints;
}
}
if(pattern.minPoints > totalPoints)
{
return ValidationResult(false);
}
return ValidationResult(true, totalPoints, transitionReplacement);
}
bool CMapEditManager::isSandType(ETerrainType::ETerrainType terType) const
{
switch(terType)
{
case ETerrainType::WATER:
case ETerrainType::SAND:
case ETerrainType::ROCK:
return true;
default:
return false;
}
}
TerrainViewPattern CMapEditManager::getFlippedPattern(const TerrainViewPattern & pattern, int flip) const
{
if(flip == 0)
{
return pattern;
}
TerrainViewPattern ret = pattern;
if(flip == FLIP_PATTERN_HORIZONTAL || flip == FLIP_PATTERN_BOTH)
{
for(int i = 0; i < 3; ++i)
{
int y = i * 3;
std::swap(ret.data[y], ret.data[y + 2]);
}
}
if(flip == FLIP_PATTERN_VERTICAL || flip == FLIP_PATTERN_BOTH)
{
for(int i = 0; i < 3; ++i)
{
std::swap(ret.data[i], ret.data[6 + i]);
}
}
return ret;
}
void CMapEditManager::insertObject(CGObjectInstance * obj, int posx, int posy, bool underground)
{
obj->pos = int3(posx, posy, underground ? 1 : 0);
obj->id = map->objects.size();
map->objects.push_back(obj);
if(obj->ID == Obj::TOWN)
{
map->towns.push_back(static_cast<CGTownInstance *>(obj));
}
if(obj->ID == Obj::HERO)
{
map->heroes.push_back(static_cast<CGHeroInstance*>(obj));
}
map->addBlockVisTiles(obj);
}
CMapEditManager::ValidationResult::ValidationResult(bool result, int points /*= 0*/, const std::string & transitionReplacement /*= ""*/)
: result(result), points(points), transitionReplacement(transitionReplacement)
{
}

View File

@ -0,0 +1,264 @@
/*
* CMapEditManager.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"
class CGObjectInstance;
namespace ETerrainGroup
{
/**
* This enumeration lists terrain groups which differ in the terrain view frames alignment.
*/
enum ETerrainGroup
{
NORMAL,
DIRT,
SAND,
WATER,
ROCK
};
}
/**
* The terrain view pattern describes a specific composition of terrain tiles
* in a 3x3 matrix and notes which terrain view frame numbers can be used.
*/
struct TerrainViewPattern
{
/** Constant for the flip mode same image. Pattern will be flipped and the same image will be used(which is given in the mapping). */
static const std::string FLIP_MODE_SAME_IMAGE;
/** Constant for the flip mode different images. Pattern will be flipped and different images will be used(mapping area is divided into 4 parts) */
static const std::string FLIP_MODE_DIFF_IMAGES;
/** Constant for the rule dirt, meaning a dirty border is required. */
static const std::string RULE_DIRT;
/** Constant for the rule sand, meaning a sandy border is required. */
static const std::string RULE_SAND;
/** Constant for the rule transition, meaning a dirty OR sandy border is required. */
static const std::string RULE_TRANSITION;
/** Constant for the rule native, meaning a native type is required. */
static const std::string RULE_NATIVE;
/** Constant for the rule any, meaning a native type, dirty OR sandy border is required. */
static const std::string RULE_ANY;
/**
* Default constructor.
*/
TerrainViewPattern();
/**
* The pattern data.
*
* It can be visualized as a 3x3 matrix:
* [ ][ ][ ]
* [ ][ ][ ]
* [ ][ ][ ]
*
* The box in the center belongs always to the native terrain type and
* is the point of origin. Depending on the terrain type different rules
* can be used. Their meaning differs also from type to type.
*
* std::vector -> several rules can be used in one cell
* std::pair -> combination of the name of the rule and a optional number of points
*/
std::array<std::vector<std::pair<std::string, int> >, 9> data;
/** The identifier of the pattern, if it's referenced from a another pattern. */
std::string id;
/**
* This describes the mapping between this pattern and the corresponding range of frames
* which should be used for the ter view.
*
* std::vector -> size=1: typical, size=2: if this pattern should map to two different types of borders
* std::pair -> 1st value: lower range, 2nd value: upper range
*/
std::vector<std::pair<int, int> > mapping;
/** The minimum points to reach to to validate the pattern successfully. */
int minPoints;
/** Describes if flipping is required and which mapping should be used. */
std::string flipMode;
/** The terrain group to which the pattern belongs to. */
ETerrainGroup::ETerrainGroup terGroup;
};
/**
* The terrain view pattern config loads pattern data from the filesystem.
*/
class CTerrainViewPatternConfig
{
public:
/**
* Constructor. Initializes the patterns data.
*/
CTerrainViewPatternConfig();
/**
* Gets the patterns for a specific group of terrain.
*
* @param terGroup the terrain group e.g. normal for grass, lava,... OR dirt OR sand,...
* @return a vector containing patterns
*/
const std::vector<TerrainViewPattern> & getPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const;
private:
/** The patterns data. */
std::map<ETerrainGroup::ETerrainGroup, std::vector<TerrainViewPattern> > patterns;
};
/**
* The map edit manager provides functionality for drawing terrain and placing
* objects on the map.
*
* TODO add undo / selection functionality for the map editor
*/
class CMapEditManager
{
public:
/**
* Constructor. The map object / terrain data has to be initialized.
*
* @param terViewPatternConfig the terrain view pattern config
* @param map the map object which should be edited
* @param randomSeed optional. the seed which is used for generating randomly terrain views
*/
CMapEditManager(const CTerrainViewPatternConfig * terViewPatternConfig, CMap * map, int randomSeed = std::time(nullptr));
/**
* Clears the terrain. The free level is filled with water and the
* underground level with rock.
*/
void clearTerrain();
/**
* Draws terrain.
*
* @param terType the type of the terrain to draw
* @param posx the x coordinate
* @param posy the y coordinate
* @param width the height of the terrain to draw
* @param height the width of the terrain to draw
* @param underground true if you want to draw at the underground, false if open
*/
void drawTerrain(ETerrainType::ETerrainType terType, int posx, int posy, int width, int height, bool underground);
/**
* Inserts an object.
*
* @param obj the object to insert
* @param posx the x coordinate
* @param posy the y coordinate
* @param underground true if you want to draw at the underground, false if open
*/
void insertObject(CGObjectInstance * obj, int posx, int posy, bool underground);
private:
/**
* The validation result struct represents the result of a pattern validation.
*/
struct ValidationResult
{
/**
* Constructor.
*
* @param result the result of the validation either true or false
* @param points optional. the points which were achieved with that pattern
* @param transitionReplacement optional. the replacement of a T rule, either D or S
*/
ValidationResult(bool result, int points = 0, const std::string & transitionReplacement = "");
/** The result of the validation. */
bool result;
/** The points which were achieved with that pattern. */
int points;
/** The replacement of a T rule, either D or S. */
std::string transitionReplacement;
};
/**
* Updates the terrain view ids in the specified area.
*
* @param posx the x coordinate
* @param posy the y coordinate
* @param width the height of the terrain to update
* @param height the width of the terrain to update
* @param mapLevel the map level, 0 for open and 1 for underground
*/
void updateTerrainViews(int posx, int posy, int width, int height, int mapLevel);
/**
* Gets the terrain group by the terrain type number.
*
* @param terType the terrain type
* @return the terrain group
*/
ETerrainGroup::ETerrainGroup getTerrainGroup(ETerrainType::ETerrainType terType) const;
/**
* Validates the terrain view of the given position and with the given pattern.
*
* @param posx the x position
* @param posy the y position
* @param mapLevel the map level, 0 for open and 1 for underground
* @param pattern the pattern to validate the terrain view with
* @return a validation result struct
*/
ValidationResult validateTerrainView(int posx, int posy, int mapLevel, const TerrainViewPattern & pattern) const;
/**
* Tests whether the given terrain type is a sand type. Sand types are: Water, Sand and Rock
*
* @param terType the terrain type to test
* @return true if the terrain type is a sand type, otherwise false
*/
bool isSandType(ETerrainType::ETerrainType terType) const;
/**
* Gets a flipped pattern.
*
* @param pattern the original pattern to flip
* @param flip the flip mode value, see FLIP_PATTERN_* constants for details
* @return the flipped pattern
*/
TerrainViewPattern getFlippedPattern(const TerrainViewPattern & pattern, int flip) const;
/** Constant for flipping a pattern horizontally. */
static const int FLIP_PATTERN_HORIZONTAL = 1;
/** Constant for flipping a pattern vertically. */
static const int FLIP_PATTERN_VERTICAL = 2;
/** Constant for flipping a pattern horizontally and vertically. */
static const int FLIP_PATTERN_BOTH = 3;
/** The map object to edit. */
CMap * map;
/** The random number generator. */
CRandomGenerator gen;
/** The terrain view pattern config. */
const CTerrainViewPatternConfig * terViewPatternConfig;
};

View File

@ -40,6 +40,6 @@ public:
template <typename Handler> void serialize(Handler &h, const int Version)
{
h & mapHeader & campaignHeader & scenarioOpts & fileURI & date & playerAmnt & humanPlayers;
h & actualHumanPlayers;
h & actualHumanPlayers & isRandomMap;
}
};

View File

@ -173,7 +173,7 @@ void CMapLoaderH3M::init()
for(int f = 0; f < map->objects.size(); ++f)
{
if(!map->objects[f]->defInfo) continue;
addBlockVisibleTiles(map->objects[f]);
map->addBlockVisTiles(map->objects[f]);
}
times.push_back(MapLoadingTime("blocked/visitable tiles", sw.getDiff()));
@ -231,8 +231,7 @@ void CMapLoaderH3M::readHeader()
void CMapLoaderH3M::readPlayerInfo()
{
mapHeader->players.resize(8);
for(int i = 0; i < 8; ++i)
for(int i = 0; i < mapHeader->players.size(); ++i)
{
mapHeader->players[i].canHumanPlay = static_cast<bool>(buffer[pos++]);
mapHeader->players[i].canComputerPlay = static_cast<bool>(buffer[pos++]);
@ -865,7 +864,7 @@ CArtifactInstance * CMapLoaderH3M::createArtifact(int aid, int spellID /*= -1*/)
a = new CArtifactInstance();
}
addNewArtifactInstance(a);
map->addNewArtifactInstance(a);
//TODO make it nicer
if(a->artType && a->artType->constituents)
@ -873,31 +872,16 @@ CArtifactInstance * CMapLoaderH3M::createArtifact(int aid, int spellID /*= -1*/)
CCombinedArtifactInstance * comb = dynamic_cast<CCombinedArtifactInstance *>(a);
BOOST_FOREACH(CCombinedArtifactInstance::ConstituentInfo & ci, comb->constituentsInfo)
{
addNewArtifactInstance(ci.art);
map->addNewArtifactInstance(ci.art);
}
}
return a;
}
void CMapLoaderH3M::addNewArtifactInstance(CArtifactInstance * art)
{
art->id = map->artInstances.size();
map->artInstances.push_back(art);
}
void CMapLoaderH3M::readTerrain()
{
// Allocate memory for terrain data
map->terrain = new TerrainTile**[map->width];
for(int ii = 0; ii < map->width; ii++)
{
map->terrain[ii] = new TerrainTile*[map->height];
for(int jj = 0; jj < map->height; jj++)
{
map->terrain[ii][jj] = new TerrainTile[map->twoLevel ? 2 : 1];
}
}
map->initTerrain();
// Read terrain
for(int a = 0; a < 2; ++a)
@ -1276,7 +1260,7 @@ void CMapLoaderH3M::readObjects()
case Obj::SEER_HUT:
{
nobj = readSeerHut();
addQuest(nobj);
map->addQuest(nobj);
break;
}
case Obj::WITCH_HUT:
@ -1584,7 +1568,7 @@ void CMapLoaderH3M::readObjects()
case Obj::QUEST_GUARD:
{
CGQuestGuard * guard = new CGQuestGuard();
addQuest(guard);
map->addQuest(guard);
readQuest(guard);
nobj = guard;
break;
@ -1672,13 +1656,13 @@ void CMapLoaderH3M::readObjects()
case Obj::BORDERGUARD:
{
nobj = new CGBorderGuard();
addQuest(nobj);
map->addQuest(nobj);
break;
}
case Obj::BORDER_GATE:
{
nobj = new CGBorderGate();
addQuest (nobj);
map->addQuest (nobj);
break;
}
case Obj::EYE_OF_MAGI:
@ -2244,17 +2228,9 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard)
guard->quest->isCustomComplete = guard->quest->completedText.size() > 0;
}
void CMapLoaderH3M::addQuest(CGObjectInstance * quest)
{
auto q = dynamic_cast<IQuestObject *>(quest);
q->quest->qid = map->quests.size();
map->quests.push_back(q->quest);
}
CGTownInstance * CMapLoaderH3M::readTown(int castleID)
{
CGTownInstance * nt = new CGTownInstance();
nt->identifier = 0;
if(map->version > EMapFormat::ROE)
{
nt->identifier = read_le_u32(buffer + pos);
@ -2421,16 +2397,8 @@ CGTownInstance * CMapLoaderH3M::readTown(int castleID)
nt->alignment = buffer[pos];
++pos;
}
else
{
nt->alignment = 0xff;
}
pos += 3;
nt->builded = 0;
nt->destroyed = 0;
nt->garrisonHero = nullptr;
return nt;
}
@ -2549,33 +2517,6 @@ void CMapLoaderH3M::readEvents()
}
}
void CMapLoaderH3M::addBlockVisibleTiles(CGObjectInstance * obj)
{
for(int fx = 0; fx < 8; ++fx)
{
for(int fy = 0; fy < 6; ++fy)
{
int xVal = obj->pos.x + fx - 7;
int yVal = obj->pos.y + fy - 5;
int zVal = obj->pos.z;
if(xVal >= 0 && xVal < map->width && yVal >= 0 && yVal < map->height)
{
TerrainTile & curt = map->terrain[xVal][yVal][zVal];
if(((obj->defInfo->visitMap[fy] >> (7 - fx)) & 1))
{
curt.visitableObjects.push_back(obj);
curt.visitable = true;
}
if(!((obj->defInfo->blockMap[fy] >> (7 - fx)) & 1))
{
curt.blockingObjects.push_back(obj);
curt.blocked = true;
}
}
}
}
}
ui8 CMapLoaderH3M::reverse(ui8 arg)
{
ui8 ret = 0;

View File

@ -225,13 +225,6 @@ private:
*/
CArtifactInstance * createArtifact(int aid, int spellID = -1);
/**
* Adds the specified artifact instance to the list of artifacts of this map.
*
* @param art the artifact which should be added to the list of artifacts
*/
void addNewArtifactInstance(CArtifactInstance * art);
/**
* Read rumors.
*/
@ -288,13 +281,6 @@ private:
*/
void readQuest(IQuestObject * guard);
/**
* Adds the specified quest instance to the list of quests.
*
* @param quest the quest object which should be added to the list of quests
*/
void addQuest(CGObjectInstance * quest);
/**
* Reads a town.
*
@ -318,13 +304,6 @@ private:
*/
void readEvents();
/**
* Adds object instance to block visitable tiles.
*
* @param obj the object to add
*/
void addBlockVisibleTiles(CGObjectInstance * obj);
/**
* Reverses the input argument.
*

View File

@ -1,8 +1,10 @@
#include "StdInc.h"
#include "CMapGenOptions.h"
#include "../GameConstants.h"
CMapGenOptions::CMapGenOptions() : width(72), height(72), hasTwoLevels(true),
playersCnt(-1), teamsCnt(-1), compOnlyPlayersCnt(0), compOnlyTeamsCnt(-1),
playersCnt(RANDOM_SIZE), teamsCnt(RANDOM_SIZE), compOnlyPlayersCnt(0), compOnlyTeamsCnt(RANDOM_SIZE),
waterContent(EWaterContent::RANDOM), monsterStrength(EMonsterStrength::RANDOM)
{
@ -13,7 +15,7 @@ si32 CMapGenOptions::getWidth() const
return width;
}
void CMapGenOptions::setWidth(int value)
void CMapGenOptions::setWidth(si32 value)
{
if(value > 0)
{
@ -21,7 +23,7 @@ void CMapGenOptions::setWidth(int value)
}
else
{
throw std::runtime_error("Map width lower than 1 not allowed.");
throw std::runtime_error("A map width lower than 1 is not allowed.");
}
}
@ -38,7 +40,7 @@ void CMapGenOptions::setHeight(si32 value)
}
else
{
throw std::runtime_error("Map height lower than 1 not allowed.");
throw std::runtime_error("A map height lower than 1 is not allowed.");
}
}
@ -59,13 +61,14 @@ si8 CMapGenOptions::getPlayersCnt() const
void CMapGenOptions::setPlayersCnt(si8 value)
{
if((value >= 1 && value <= 8) || value == RANDOM_SIZE)
if((value >= 1 && value <= GameConstants::PLAYER_LIMIT) || value == RANDOM_SIZE)
{
playersCnt = value;
}
else
{
throw std::runtime_error("Players count of RMG options should be between 1 and 8 or -1 for random.");
throw std::runtime_error("Players count of RMG options should be between 1 and " +
boost::lexical_cast<std::string>(GameConstants::PLAYER_LIMIT) + " or CMapGenOptions::RANDOM_SIZE for random.");
}
}
@ -83,7 +86,7 @@ void CMapGenOptions::setTeamsCnt(si8 value)
else
{
throw std::runtime_error("Teams count of RMG options should be between 0 and <" +
boost::lexical_cast<std::string>(playersCnt) + "(players count) - 1> or -1 for random.");
boost::lexical_cast<std::string>(playersCnt) + "(players count) - 1> or CMapGenOptions::RANDOM_SIZE for random.");
}
}
@ -94,15 +97,15 @@ si8 CMapGenOptions::getCompOnlyPlayersCnt() const
void CMapGenOptions::setCompOnlyPlayersCnt(si8 value)
{
if(value == RANDOM_SIZE || (value >= 0 && value <= 8 - playersCnt))
if(value == RANDOM_SIZE || (value >= 0 && value <= GameConstants::PLAYER_LIMIT - playersCnt))
{
compOnlyPlayersCnt = value;
}
else
{
throw std::runtime_error(std::string("Computer only players count of RMG options should be ") +
"between 0 and <8 - " + boost::lexical_cast<std::string>(playersCnt) +
"(playersCount)> or -1 for random.");
"between 0 and <GameConstants::PLAYER_LIMIT - " + boost::lexical_cast<std::string>(playersCnt) +
"(playersCount)> or CMapGenOptions::RANDOM_SIZE for random.");
}
}
@ -113,7 +116,7 @@ si8 CMapGenOptions::getCompOnlyTeamsCnt() const
void CMapGenOptions::setCompOnlyTeamsCnt(si8 value)
{
if(value == RANDOM_SIZE || compOnlyPlayersCnt == RANDOM_SIZE || (value >= 0 && value <= compOnlyPlayersCnt - 1))
if(value == RANDOM_SIZE || compOnlyPlayersCnt == RANDOM_SIZE || (value >= 0 && value <= std::max(compOnlyPlayersCnt - 1, 0)))
{
compOnlyTeamsCnt = value;
}
@ -121,7 +124,7 @@ void CMapGenOptions::setCompOnlyTeamsCnt(si8 value)
{
throw std::runtime_error(std::string("Computer only teams count of RMG options should be ") +
"between 0 and <" + boost::lexical_cast<std::string>(compOnlyPlayersCnt) +
"(compOnlyPlayersCnt) - 1> or -1 for random.");
"(compOnlyPlayersCnt) - 1> or CMapGenOptions::RANDOM_SIZE for random.");
}
}

View File

@ -89,61 +89,61 @@ public:
void setHasTwoLevels(bool value);
/**
* Gets the count of the players. The default value is -1 representing a random
* Gets the count of the players. The default value is RANDOM_SIZE representing a random
* player count.
*
* @return the count of the players ranging from 1 to 8 or -1 for random
* @return the count of the players ranging from 1 to GameConstants::PLAYER_LIMIT or RANDOM_SIZE for random
*/
si8 getPlayersCnt() const;
/**
* Sets the count of the players.
*
* @param value the count of the players ranging from 1 to 8, -1 for random
* @param value the count of the players ranging from 1 to GameConstants::PLAYER_LIMIT, RANDOM_SIZE for random
*/
void setPlayersCnt(si8 value);
/**
* Gets the count of the teams. The default value is -1 representing a random
* Gets the count of the teams. The default value is RANDOM_SIZE representing a random
* team count.
*
* @return the count of the teams ranging from 0 to <players count - 1> or -1 for random
* @return the count of the teams ranging from 0 to <players count - 1> or RANDOM_SIZE for random
*/
si8 getTeamsCnt() const;
/**
* Sets the count of the teams
*
* @param value the count of the teams ranging from 0 to <players count - 1>, -1 for random
* @param value the count of the teams ranging from 0 to <players count - 1>, RANDOM_SIZE for random
*/
void setTeamsCnt(si8 value);
/**
* Gets the count of the computer only players. The default value is 0.
*
* @return the count of the computer only players ranging from 0 to <8 - players count> or -1 for random
* @return the count of the computer only players ranging from 0 to <GameConstants::PLAYER_LIMIT - players count> or RANDOM_SIZE for random
*/
si8 getCompOnlyPlayersCnt() const;
/**
* Sets the count of the computer only players.
*
* @param value the count of the computer only players ranging from 0 to <8 - players count>, -1 for random
* @param value the count of the computer only players ranging from 0 to <GameConstants::PLAYER_LIMIT - players count>, RANDOM_SIZE for random
*/
void setCompOnlyPlayersCnt(si8 value);
/**
* Gets the count of the computer only teams. The default value is -1 representing
* Gets the count of the computer only teams. The default value is RANDOM_SIZE representing
* a random computer only team count.
*
* @return the count of the computer only teams ranging from 0 to <comp only players - 1> or -1 for random
* @return the count of the computer only teams ranging from 0 to <comp only players - 1> or RANDOM_SIZE for random
*/
si8 getCompOnlyTeamsCnt() const;
/**
* Sets the count of the computer only teams.
*
* @param value the count of the computer only teams ranging from 0 to <comp only players - 1>, -1 for random
* @param value the count of the computer only teams ranging from 0 to <comp only players - 1>, RANDOM_SIZE for random
*/
void setCompOnlyTeamsCnt(si8 value);
@ -213,6 +213,8 @@ public:
template <typename Handler>
void serialize(Handler & h, const int version)
{
//FIXME: Enum is not a fixed with data type. Add enum class to both enums
// later. For now it is ok.
h & width & height & hasTwoLevels & playersCnt & teamsCnt & compOnlyPlayersCnt;
h & compOnlyTeamsCnt & waterContent & monsterStrength;
}

399
lib/RMG/CMapGenerator.cpp Normal file
View File

@ -0,0 +1,399 @@
#include "StdInc.h"
#include "CMapGenerator.h"
#include "../Mapping/CMap.h"
#include "../VCMI_Lib.h"
#include "../CGeneralTextHandler.h"
#include "../Mapping/CMapEditManager.h"
#include "../CObjectHandler.h"
#include "../CDefObjInfoHandler.h"
#include "../GameConstants.h"
#include "../CTownHandler.h"
#include "../StringConstants.h"
CMapGenerator::CMapGenerator(const CMapGenOptions & mapGenOptions, const std::map<TPlayerColor, CPlayerSettings> & players, int randomSeed) :
mapGenOptions(mapGenOptions), randomSeed(randomSeed), players(players)
{
gen.seed(randomSeed);
validateOptions();
}
CMapGenerator::~CMapGenerator()
{
}
std::unique_ptr<CMap> CMapGenerator::generate()
{
finalizeMapGenOptions();
//TODO select a template based on the map gen options or adapt it if necessary
map = std::unique_ptr<CMap>(new CMap());
addHeaderInfo();
terViewPatternConfig = std::unique_ptr<CTerrainViewPatternConfig>(new CTerrainViewPatternConfig());
mapMgr = std::unique_ptr<CMapEditManager>(new CMapEditManager(terViewPatternConfig.get(), map.get(), randomSeed));
genTerrain();
genTowns();
return std::move(map);
}
void CMapGenerator::validateOptions() const
{
int playersCnt = 0;
int compOnlyPlayersCnt = 0;
BOOST_FOREACH(const tPlayersMap::value_type & pair, players)
{
if(pair.second.getPlayerType() == CPlayerSettings::COMP_ONLY)
{
++compOnlyPlayersCnt;
}
else
{
++playersCnt;
}
}
if(mapGenOptions.getPlayersCnt() == CMapGenOptions::RANDOM_SIZE)
{
if(playersCnt != GameConstants::PLAYER_LIMIT)
{
throw std::runtime_error(std::string("If the count of players is random size, ")
+ "the count of the items in the players map should equal GameConstants::PLAYER_LIMIT.");
}
if(playersCnt == mapGenOptions.getPlayersCnt())
{
throw std::runtime_error(std::string("If the count of players is random size, ")
+ "all items in the players map should be either of player type AI or HUMAN.");
}
}
else
{
if(mapGenOptions.getCompOnlyPlayersCnt() != CMapGenOptions::RANDOM_SIZE)
{
if(playersCnt != mapGenOptions.getPlayersCnt() || compOnlyPlayersCnt != mapGenOptions.getCompOnlyPlayersCnt())
{
throw std::runtime_error(std::string("The count of players and computer only players in the players map ")
+ "doesn't conform with the specified map gen options.");
}
}
else
{
if(playersCnt != mapGenOptions.getPlayersCnt() || (playersCnt == mapGenOptions.getPlayersCnt()
&& compOnlyPlayersCnt != GameConstants::PLAYER_LIMIT - playersCnt))
{
throw std::runtime_error(std::string("If the count of players is fixed and the count of comp only players random, ")
+ "the items in the players map should equal GameConstants::PLAYER_LIMIT.");
}
}
}
if(countHumanPlayers() < 1)
{
throw std::runtime_error("1 human player is required at least");
}
BOOST_FOREACH(const tPlayersMap::value_type & pair, players)
{
if(pair.first != pair.second.getColor())
{
throw std::runtime_error("The color of an item in player settings and the key of it has to be the same.");
}
}
}
void CMapGenerator::finalizeMapGenOptions()
{
if(mapGenOptions.getPlayersCnt() == CMapGenOptions::RANDOM_SIZE)
{
mapGenOptions.setPlayersCnt(gen.getInteger(countHumanPlayers(), GameConstants::PLAYER_LIMIT));
// Remove AI players only from the end of the players map if necessary
for(auto itrev = players.end(); itrev != players.begin();)
{
auto it = itrev;
--it;
if(players.size() == mapGenOptions.getPlayersCnt())
{
break;
}
const CPlayerSettings & pSettings = it->second;
if(pSettings.getPlayerType() == CPlayerSettings::AI)
{
players.erase(it);
}
else
{
--itrev;
}
}
}
if(mapGenOptions.getTeamsCnt() == CMapGenOptions::RANDOM_SIZE)
{
mapGenOptions.setTeamsCnt(gen.getInteger(0, mapGenOptions.getPlayersCnt() - 1));
}
if(mapGenOptions.getCompOnlyPlayersCnt() == CMapGenOptions::RANDOM_SIZE)
{
mapGenOptions.setCompOnlyPlayersCnt(gen.getInteger(0, 8 - mapGenOptions.getPlayersCnt()));
int totalPlayersCnt = mapGenOptions.getPlayersCnt() + mapGenOptions.getCompOnlyPlayersCnt();
// Remove comp only players only from the end of the players map if necessary
for(auto itrev = players.end(); itrev != players.begin();)
{
auto it = itrev;
--it;
if(players.size() <= totalPlayersCnt)
{
break;
}
const CPlayerSettings & pSettings = it->second;
if(pSettings.getPlayerType() == CPlayerSettings::COMP_ONLY)
{
players.erase(it);
}
else
{
--itrev;
}
}
// Add some comp only players if necessary
int compOnlyPlayersToAdd = totalPlayersCnt - players.size();
for(int i = 0; i < compOnlyPlayersToAdd; ++i)
{
CPlayerSettings pSettings;
pSettings.setPlayerType(CPlayerSettings::COMP_ONLY);
pSettings.setColor(getNextPlayerColor());
players[pSettings.getColor()] = pSettings;
}
}
if(mapGenOptions.getCompOnlyTeamsCnt() == CMapGenOptions::RANDOM_SIZE)
{
mapGenOptions.setCompOnlyTeamsCnt(gen.getInteger(0, std::max(mapGenOptions.getCompOnlyPlayersCnt() - 1, 0)));
}
// There should be at least 2 players (1-player-maps aren't allowed)
if(mapGenOptions.getPlayersCnt() + mapGenOptions.getCompOnlyPlayersCnt() < 2)
{
CPlayerSettings pSettings;
pSettings.setPlayerType(CPlayerSettings::AI);
pSettings.setColor(getNextPlayerColor());
players[pSettings.getColor()] = pSettings;
mapGenOptions.setPlayersCnt(2);
}
// 1 team isn't allowed
if(mapGenOptions.getTeamsCnt() == 1 && mapGenOptions.getCompOnlyPlayersCnt() == 0)
{
mapGenOptions.setTeamsCnt(0);
}
if(mapGenOptions.getWaterContent() == EWaterContent::RANDOM)
{
mapGenOptions.setWaterContent(static_cast<EWaterContent::EWaterContent>(gen.getInteger(0, 2)));
}
if(mapGenOptions.getMonsterStrength() == EMonsterStrength::RANDOM)
{
mapGenOptions.setMonsterStrength(static_cast<EMonsterStrength::EMonsterStrength>(gen.getInteger(0, 2)));
}
}
std::string CMapGenerator::getMapDescription() const
{
const std::string waterContentStr[3] = { "none", "normal", "islands" };
const std::string monsterStrengthStr[3] = { "weak", "normal", "strong" };
std::stringstream ss;
ss << "Map created by the Random Map Generator.\nTemplate was <MOCK>, ";
ss << "Random seed was " << randomSeed << ", size " << map->width << "x";
ss << map->height << ", levels " << (map->twoLevel ? "2" : "1") << ", ";
ss << "humans " << static_cast<int>(mapGenOptions.getPlayersCnt()) << ", computers ";
ss << static_cast<int>(mapGenOptions.getCompOnlyPlayersCnt()) << ", water " << waterContentStr[mapGenOptions.getWaterContent()];
ss << ", monster " << monsterStrengthStr[mapGenOptions.getMonsterStrength()] << ", second expansion map";
BOOST_FOREACH(const tPlayersMap::value_type & pair, players)
{
const CPlayerSettings & pSettings = pair.second;
if(pSettings.getPlayerType() == CPlayerSettings::HUMAN)
{
ss << ", " << GameConstants::PLAYER_COLOR_NAMES[pSettings.getColor()] << " is human";
}
if(pSettings.getStartingTown() != CPlayerSettings::RANDOM_TOWN)
{
ss << ", " << GameConstants::PLAYER_COLOR_NAMES[pSettings.getColor()]
<< " town choice is " << ETownType::names[pSettings.getStartingTown()];
}
}
return ss.str();
}
void CMapGenerator::addPlayerInfo()
{
// Calculate which team numbers exist
std::array<std::list<int>, 2> teamNumbers; // 0= cpu/human, 1= cpu only
int teamOffset = 0;
for(int i = 0; i < 2; ++i)
{
int playersCnt = i == 0 ? mapGenOptions.getPlayersCnt() : mapGenOptions.getCompOnlyPlayersCnt();
int teamsCnt = i == 0 ? mapGenOptions.getTeamsCnt() : mapGenOptions.getCompOnlyTeamsCnt();
if(playersCnt == 0)
{
continue;
}
int playersPerTeam = playersCnt /
(teamsCnt == 0 ? playersCnt : teamsCnt);
int teamsCntNorm = teamsCnt;
if(teamsCntNorm == 0)
{
teamsCntNorm = playersCnt;
}
for(int j = 0; j < teamsCntNorm; ++j)
{
for(int k = 0; k < playersPerTeam; ++k)
{
teamNumbers[i].push_back(j + teamOffset);
}
}
for(int j = 0; j < playersCnt - teamsCntNorm * playersPerTeam; ++j)
{
teamNumbers[i].push_back(j + teamOffset);
}
teamOffset += teamsCntNorm;
}
// Team numbers are assigned randomly to every player
BOOST_FOREACH(const tPlayersMap::value_type & pair, players)
{
const CPlayerSettings & pSettings = pair.second;
PlayerInfo player;
player.canComputerPlay = true;
int j = pSettings.getPlayerType() == CPlayerSettings::COMP_ONLY ? 1 : 0;
if(j == 0)
{
player.canHumanPlay = true;
}
auto itTeam = std::next(teamNumbers[j].begin(), gen.getInteger(0, teamNumbers[j].size() - 1));
player.team = *itTeam;
teamNumbers[j].erase(itTeam);
map->players[pSettings.getColor()] = player;
}
map->howManyTeams = (mapGenOptions.getTeamsCnt() == 0 ? mapGenOptions.getPlayersCnt() : mapGenOptions.getTeamsCnt())
+ (mapGenOptions.getCompOnlyTeamsCnt() == 0 ? mapGenOptions.getCompOnlyPlayersCnt() : mapGenOptions.getCompOnlyTeamsCnt());
}
int CMapGenerator::countHumanPlayers() const
{
return static_cast<int>(std::count_if(players.begin(), players.end(), [](const std::pair<TPlayerColor, CPlayerSettings> & pair)
{
return pair.second.getPlayerType() == CPlayerSettings::HUMAN;
}));
}
void CMapGenerator::genTerrain()
{
map->initTerrain(); //FIXME nicer solution
mapMgr->clearTerrain();
mapMgr->drawTerrain(ETerrainType::GRASS, 10, 10, 20, 30, false);
}
void CMapGenerator::genTowns()
{
//FIXME mock gen
const int3 townPos[2] = { int3(17, 13, 0), int3(25,13, 0) };
const TFaction townTypes[2] = { ETownType::CASTLE, ETownType::DUNGEON };
for(auto it = players.begin(); it != players.end(); ++it)
{
TPlayerColor owner = it->first;
int pos = std::distance(players.begin(), it);
int side = pos % 2;
CGTownInstance * town = new CGTownInstance();
town->ID = Obj::TOWN;
town->subID = townTypes[side];
town->tempOwner = owner;
town->defInfo = VLC->dobjinfo->gobjs[town->ID][town->subID];
town->builtBuildings.insert(EBuilding::FORT);
town->builtBuildings.insert(-50);
mapMgr->insertObject(town, townPos[side].x, townPos[side].y + (pos / 2) * 5, false);
map->players[owner].allowedFactions.clear();
map->players[owner].allowedFactions.insert(townTypes[side]);
}
}
void CMapGenerator::addHeaderInfo()
{
map->version = EMapFormat::SOD;
map->width = mapGenOptions.getWidth();
map->height = mapGenOptions.getHeight();
map->twoLevel = mapGenOptions.getHasTwoLevels();
map->name = VLC->generaltexth->allTexts[740];
map->description = getMapDescription();
map->difficulty = 1;
addPlayerInfo();
}
TPlayerColor CMapGenerator::getNextPlayerColor() const
{
for(TPlayerColor i = 0; i < GameConstants::PLAYER_LIMIT; ++i)
{
if(players.find(i) == players.end())
{
return i;
}
}
throw std::runtime_error("Shouldn't happen. No free player color exists.");
}
CMapGenerator::CPlayerSettings::CPlayerSettings() : color(0), startingTown(RANDOM_TOWN), playerType(AI)
{
}
int CMapGenerator::CPlayerSettings::getColor() const
{
return color;
}
void CMapGenerator::CPlayerSettings::setColor(int value)
{
if(value >= 0 && value < GameConstants::PLAYER_LIMIT)
{
color = value;
}
else
{
throw std::runtime_error("The color of the player is not in a valid range.");
}
}
int CMapGenerator::CPlayerSettings::getStartingTown() const
{
return startingTown;
}
void CMapGenerator::CPlayerSettings::setStartingTown(int value)
{
if(value >= -1 && value < static_cast<int>(VLC->townh->towns.size()))
{
startingTown = value;
}
else
{
throw std::runtime_error("The starting town of the player is not in a valid range.");
}
}
CMapGenerator::CPlayerSettings::EPlayerType CMapGenerator::CPlayerSettings::getPlayerType() const
{
return playerType;
}
void CMapGenerator::CPlayerSettings::setPlayerType(EPlayerType value)
{
playerType = value;
}

199
lib/RMG/CMapGenerator.h Normal file
View File

@ -0,0 +1,199 @@
/*
* CMapGenerator.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 "CMapGenOptions.h"
#include "../CRandomGenerator.h"
class CMap;
class CTerrainViewPatternConfig;
class CMapEditManager;
/**
* The map generator creates a map randomly.
*/
class CMapGenerator
{
public:
/**
* The player settings class maps the player color, starting town and human player flag.
*/
class CPlayerSettings
{
public:
enum EPlayerType
{
HUMAN,
AI,
COMP_ONLY
};
/**
* Constructor.
*/
CPlayerSettings();
/**
* Gets the color of the player. The default value is 0.
*
* @return the color of the player ranging from 0 to GameConstants::PLAYER_LIMIT - 1
*/
int getColor() const;
/**
* Sets the color of the player.
*
* @param value the color of the player ranging from 0 to GameConstants::PLAYER_LIMIT - 1
*/
void setColor(int value);
/**
* Gets the starting town of the player. The default value is RANDOM_TOWN.
*
* @return the starting town of the player ranging from 0 to town max count or RANDOM_TOWN
*/
int getStartingTown() const;
/**
* Sets the starting town of the player.
*
* @param value the starting town of the player ranging from 0 to town max count or RANDOM_TOWN
*/
void setStartingTown(int value);
/**
* Gets the type of the player. The default value is EPlayerType::AI.
*
* @return the type of the player
*/
EPlayerType getPlayerType() const;
/**
* Sets the type of the player.
*
* @param playerType the type of the player
*/
void setPlayerType(EPlayerType value);
/** Constant for a random town selection. */
static const int RANDOM_TOWN = -1;
private:
/** The color of the player. */
int color;
/** The starting town of the player. */
int startingTown;
/** The type of the player e.g. human, comp only,... */
EPlayerType playerType;
};
/**
* Constructor.
*
* @param mapGenOptions these options describe how to generate the map.
* @param players the random gen player settings
* @param randomSeed a random seed is required to get random numbers.
*/
CMapGenerator(const CMapGenOptions & mapGenOptions, const std::map<TPlayerColor, CPlayerSettings> & players, int randomSeed);
/**
* Destructor.
*/
~CMapGenerator();
/**
* Generates a map.
*
* @return the generated map object stored in a unique ptr
*/
std::unique_ptr<CMap> generate();
private:
/**
* Validates map gen options and players options. On errors exceptions will be thrown.
*/
void validateOptions() const;
/**
* Finalizes map generation options. Random sizes for various properties are
* converted to fixed values.
*/
void finalizeMapGenOptions();
/**
* Gets the map description of the generated map.
*
* @return the map description of the generated map
*/
std::string getMapDescription() const;
/**
* Adds player information.(teams, colors, etc...)
*/
void addPlayerInfo();
/**
* Counts the amount of human players.
*
* @return the amount of human players ranging from 0 to GameConstants::PLAYER_LIMIT
*/
int countHumanPlayers() const;
/**
* Generate terrain.
*/
void genTerrain();
/**
* Generate towns.
*/
void genTowns();
/**
* Adds header info(size, description, etc...)
*/
void addHeaderInfo();
/**
* Gets the next free player color.
*
* @return the next free player color
*/
TPlayerColor getNextPlayerColor() const;
/** The map options which describes the size of the map and contain player info. */
CMapGenOptions mapGenOptions;
/** The generated map. */
std::unique_ptr<CMap> map;
/** The random number generator. */
CRandomGenerator gen;
/** The random seed, it is used for the map description. */
int randomSeed;
/** The terrain view pattern config. */
std::unique_ptr<CTerrainViewPatternConfig> terViewPatternConfig;
/** The map edit manager. */
std::unique_ptr<CMapEditManager> mapMgr;
/** The random gen player settings. */
std::map<TPlayerColor, CPlayerSettings> players;
/** Typedef of the players map, so that boost foreach can be used. */
typedef std::map<TPlayerColor, CPlayerSettings> tPlayersMap;
};

View File

@ -42,6 +42,7 @@ struct PlayerSettings
std::string name;
ui8 playerID; //0 - AI, non-0 serves as player id
bool compOnly; //true if this player is a computer only player; required for RMG
template <typename Handler>
void serialize(Handler &h, const int version)
{
@ -55,13 +56,12 @@ struct PlayerSettings
h & name;
h & playerID;
h & team;
h & compOnly;
}
PlayerSettings()
PlayerSettings() : bonus(RANDOM), castle(NONE), heroPortrait(RANDOM), compOnly(false)
{
bonus = RANDOM;
castle = NONE;
heroPortrait = RANDOM;
}
};

View File

@ -31,6 +31,9 @@ namespace GameConstants
"demoniac", "heretic", "deathknight", "necromancer", "warlock", "overlord",
"barbarian", "battlemage", "beastmaster", "witch", "planeswalker", "elementalist"
};
const std::string PLAYER_COLOR_NAMES [PLAYER_LIMIT] = {
"red", "blue", "tan", "green", "orange", "purple", "teal", "pink"
};
}
namespace EAlignment

View File

@ -197,7 +197,9 @@
<ClCompile Include="Mapping\CMap.cpp" />
<ClCompile Include="Mapping\CMapInfo.cpp" />
<ClCompile Include="Mapping\CMapService.cpp" />
<ClCompile Include="Mapping\CMapEditManager.cpp" />
<ClCompile Include="RMG\CMapGenOptions.cpp" />
<ClCompile Include="RMG\CMapGenerator.cpp" />
<ClCompile Include="HeroBonus.cpp" />
<ClCompile Include="CBattleCallback.cpp" />
<ClCompile Include="IGameCallback.cpp" />
@ -239,6 +241,7 @@
<ClInclude Include="CondSh.h" />
<ClInclude Include="Connection.h" />
<ClInclude Include="ConstTransitivePtr.h" />
<ClInclude Include="CRandomGenerator.h" />
<ClInclude Include="CScriptingModule.h" />
<ClInclude Include="CSpellHandler.h" />
<ClInclude Include="CStopWatch.h" />
@ -258,7 +261,9 @@
<ClInclude Include="Mapping\CMap.h" />
<ClInclude Include="Mapping\CMapInfo.h" />
<ClInclude Include="Mapping\CMapService.h" />
<ClInclude Include="Mapping\CMapEditManager.h" />
<ClInclude Include="RMG\CMapGenOptions.h" />
<ClInclude Include="RMG\CMapGenerator.h" />
<ClInclude Include="GameConstants.h" />
<ClInclude Include="HeroBonus.h" />
<ClInclude Include="CBattleCallback.h" />