diff --git a/Global.h b/Global.h index 8fa1c9898..c0edb6ade 100644 --- a/Global.h +++ b/Global.h @@ -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 diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index 61d0cb92d..18e844536 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -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 &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 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 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 monsterStrengthBtns = boost::assign::list_of("RANWEAK")("RANNORM")("RANSTRG"); addButtonsWithRandToGroup(monsterStrengthGroup, monsterStrengthBtns, 0, 2, WIDE_BTN_WIDTH, 248, 251); monsterStrengthGroup->onChange = [&](int btnId) diff --git a/client/UIFramework/CGuiHandler.cpp b/client/UIFramework/CGuiHandler.cpp index f24f611c1..fe434751d 100644 --- a/client/UIFramework/CGuiHandler.cpp +++ b/client/UIFramework/CGuiHandler.cpp @@ -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); } } diff --git a/config/terrainViewPatterns.json b/config/terrainViewPatterns.json new file mode 100644 index 000000000..888be7f93 --- /dev/null +++ b/config/terrainViewPatterns.json @@ -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 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 + } + ] +} diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index cc55a0968..eeee83bcd 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -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 &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 CArtHandler::getDefaultAllowedArtifacts() const +{ + std::vector allowedArtifacts; + allowedArtifacts.resize(127, 1); + allowedArtifacts.resize(141, 0); + allowedArtifacts.resize(GameConstants::ARTIFACTS_QUANTITY, 1); + return allowedArtifacts; +} + CArtifactInstance::CArtifactInstance() { init(); diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index 15ee1e854..d9674b69d 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -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 getDefaultAllowedArtifacts() const; + template void serialize(Handler &h, const int version) { h & artifacts & allowedArtifacts & treasures & minors & majors & relics diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index d74e137c3..22c9cdd03 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -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 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): "<checksum << std::endl; if(scenarioOps->mapfileChecksum) diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 4f8da6332..eddbf8569 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -498,3 +498,10 @@ std::vector CHeroHandler::getDefaultAllowedHeroes() const return allowedHeroes; } + +std::vector CHeroHandler::getDefaultAllowedAbilities() const +{ + std::vector allowedAbilities; + allowedAbilities.resize(GameConstants::SKILL_QUANTITY, 1); + return allowedAbilities; +} diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 0fa57919c..e8d88c9b9 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -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 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 getDefaultAllowedAbilities() const; + template void serialize(Handler &h, const int version) { h & classes & heroes & expPerLevel & ballistics & terrCosts; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 6af4b513d..9ca12ef09 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,79 +1,82 @@ -project(libvcmi) -cmake_minimum_required(VERSION 2.6) - -include_directories(${CMAKE_HOME_DIRECTORY} ${CMAKE_CURRENT_SOURCE_DIRECTORY} ${CMAKE_HOME_DIRECTORY}/lib) -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 -) - -set(lib_HEADERS - Filesystem/CInputStream.h - Filesystem/ISimpleResourceLoader.h - AI_Base.h - CondSh.h - ConstTransitivePtr.h - CScriptingModule.h - CStopWatch.h - GameConstants.h - StringConstants.h - IGameEventsReceiver.h - int3.h - Interprocess.h - NetPacks.h - RegisterTypes.h - StartInfo.h - UnlockGuard.h - VCMIDirs.h - vcmi_endian.h -) - -add_library(vcmi SHARED ${lib_SRCS} ${lib_HEADERS}) -set_target_properties(vcmi PROPERTIES XCODE_ATTRIBUTE_LD_DYLIB_INSTALL_NAME "@executable_path/libvcmi.dylib") -target_link_libraries(vcmi ${Boost_LIBRARIES} ${SDL_LIBRARY} ${ZLIB_LIBRARIES}) - -if (NOT APPLE) # Already inside vcmiclient bundle - install(TARGETS vcmi DESTINATION ${LIB_DIR}) -endif() +project(libvcmi) +cmake_minimum_required(VERSION 2.6) + +include_directories(${CMAKE_HOME_DIRECTORY} ${CMAKE_CURRENT_SOURCE_DIRECTORY} ${CMAKE_HOME_DIRECTORY}/lib) +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/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 + Filesystem/CInputStream.h + Filesystem/ISimpleResourceLoader.h + AI_Base.h + CondSh.h + ConstTransitivePtr.h + CRandomGenerator.h + CScriptingModule.h + CStopWatch.h + GameConstants.h + StringConstants.h + IGameEventsReceiver.h + int3.h + Interprocess.h + NetPacks.h + RegisterTypes.h + StartInfo.h + UnlockGuard.h + VCMIDirs.h + vcmi_endian.h +) + +add_library(vcmi SHARED ${lib_SRCS} ${lib_HEADERS}) +set_target_properties(vcmi PROPERTIES XCODE_ATTRIBUTE_LD_DYLIB_INSTALL_NAME "@executable_path/libvcmi.dylib") +target_link_libraries(vcmi ${Boost_LIBRARIES} ${SDL_LIBRARY} ${ZLIB_LIBRARIES}) + +if (NOT APPLE) # Already inside vcmiclient bundle + install(TARGETS vcmi DESTINATION ${LIB_DIR}) +endif() diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp index 164154236..9a0d05b31 100644 --- a/lib/CObjectHandler.cpp +++ b/lib/CObjectHandler.cpp @@ -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() diff --git a/lib/CRandomGenerator.h b/lib/CRandomGenerator.h new file mode 100644 index 000000000..af960553b --- /dev/null +++ b/lib/CRandomGenerator.h @@ -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 +#include +#include +#include + +typedef boost::mt19937 TGenerator; +typedef boost::random::uniform_int_distribution TIntDist; +typedef boost::random::uniform_real_distribution TRealDist; +typedef boost::variate_generator TRandI; +typedef boost::variate_generator 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; +}; diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index 54cdc834e..87fb8059f 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -378,3 +378,10 @@ void CSpellHandler::loadSpells() risingSpells += 38, 39, 40; mindSpells += 50, 59, 60, 61, 62; } + +std::vector CSpellHandler::getDefaultAllowedSpells() const +{ + std::vector allowedSpells; + allowedSpells.resize(GameConstants::SPELLS_QUANTITY, 1); + return allowedSpells; +} diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index 1114c78ae..1c12e2fdb 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -101,6 +101,13 @@ public: std::set 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 getDefaultAllowedSpells() const; + template void serialize(Handler &h, const int version) { h & spells & damageSpells & risingSpells & mindSpells; diff --git a/lib/Mapping/CMap.cpp b/lib/Mapping/CMap.cpp index d75379235..ec15ac467 100644 --- a/lib/Mapping/CMap.cpp +++ b/lib/Mapping/CMap.cpp @@ -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(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]; + } + } +} diff --git a/lib/Mapping/CMap.h b/lib/Mapping/CMap.h index 81d309eca..dac0cf4b4 100644 --- a/lib/Mapping/CMap.h +++ b/lib/Mapping/CMap.h @@ -116,10 +116,10 @@ struct DLL_LINKAGE PlayerInfo /** The list of renamed heroes. */ std::vector 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 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 */ diff --git a/lib/Mapping/CMapEditManager.cpp b/lib/Mapping/CMapEditManager.cpp new file mode 100644 index 000000000..cd8e2461a --- /dev/null +++ b/lib/Mapping/CMapEditManager.cpp @@ -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 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 rules; + boost::split(rules, cell, boost::is_any_of(",")); + BOOST_FOREACH(std::string ruleStr, rules) + { + std::vector rule; + boost::split(rule, ruleStr, boost::is_any_of("-")); + std::pair pair; + pair.first = rule[0]; + if(rule.size() > 1) + { + pair.second = boost::lexical_cast(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 mappings; + boost::split(mappings, mappingStr, boost::is_any_of(",")); + BOOST_FOREACH(std::string mapping, mappings) + { + std::vector range; + boost::split(range, mapping, boost::is_any_of("-")); + pattern.mapping.push_back(std::make_pair(boost::lexical_cast(range[0]), + boost::lexical_cast(range.size() > 1 ? range[1] : range[0]))); + } + + // Read optional attributes + pattern.id = ptrnNode["id"].String(); + pattern.minPoints = static_cast(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 & 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 & 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 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 & 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(obj)); + } + if(obj->ID == Obj::HERO) + { + map->heroes.push_back(static_cast(obj)); + } + map->addBlockVisTiles(obj); +} + +CMapEditManager::ValidationResult::ValidationResult(bool result, int points /*= 0*/, const std::string & transitionReplacement /*= ""*/) + : result(result), points(points), transitionReplacement(transitionReplacement) +{ + +} diff --git a/lib/Mapping/CMapEditManager.h b/lib/Mapping/CMapEditManager.h new file mode 100644 index 000000000..cf6f06f2f --- /dev/null +++ b/lib/Mapping/CMapEditManager.h @@ -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 >, 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 > 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 & getPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const; + +private: + /** The patterns data. */ + std::map > 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; +}; diff --git a/lib/Mapping/CMapInfo.h b/lib/Mapping/CMapInfo.h index 17c97b73a..24154d6bf 100644 --- a/lib/Mapping/CMapInfo.h +++ b/lib/Mapping/CMapInfo.h @@ -40,6 +40,6 @@ public: template void serialize(Handler &h, const int Version) { h & mapHeader & campaignHeader & scenarioOpts & fileURI & date & playerAmnt & humanPlayers; - h & actualHumanPlayers; + h & actualHumanPlayers & isRandomMap; } }; diff --git a/lib/Mapping/CMapService.cpp b/lib/Mapping/CMapService.cpp index 8188f83b4..22c59073b 100644 --- a/lib/Mapping/CMapService.cpp +++ b/lib/Mapping/CMapService.cpp @@ -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(buffer[pos++]); mapHeader->players[i].canComputerPlay = static_cast(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(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(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; diff --git a/lib/Mapping/CMapService.h b/lib/Mapping/CMapService.h index b9553e904..15bd51920 100644 --- a/lib/Mapping/CMapService.h +++ b/lib/Mapping/CMapService.h @@ -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. * diff --git a/lib/RMG/CMapGenOptions.cpp b/lib/RMG/CMapGenOptions.cpp index 172c37eb2..df669623f 100644 --- a/lib/RMG/CMapGenOptions.cpp +++ b/lib/RMG/CMapGenOptions.cpp @@ -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(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(playersCnt) + "(players count) - 1> or -1 for random."); + boost::lexical_cast(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(playersCnt) + - "(playersCount)> or -1 for random."); + "between 0 and (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(compOnlyPlayersCnt) + - "(compOnlyPlayersCnt) - 1> or -1 for random."); + "(compOnlyPlayersCnt) - 1> or CMapGenOptions::RANDOM_SIZE for random."); } } diff --git a/lib/RMG/CMapGenOptions.h b/lib/RMG/CMapGenOptions.h index b77f1ade5..e823736d0 100644 --- a/lib/RMG/CMapGenOptions.h +++ b/lib/RMG/CMapGenOptions.h @@ -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 or -1 for random + * @return the count of the teams ranging from 0 to 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 , -1 for random + * @param value the count of the teams ranging from 0 to , 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 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 , 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 or -1 for random + * @return the count of the computer only teams ranging from 0 to 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 , -1 for random + * @param value the count of the computer only teams ranging from 0 to , RANDOM_SIZE for random */ void setCompOnlyTeamsCnt(si8 value); @@ -213,6 +213,8 @@ public: template 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; } diff --git a/lib/RMG/CMapGenerator.cpp b/lib/RMG/CMapGenerator.cpp new file mode 100644 index 000000000..2e0f44205 --- /dev/null +++ b/lib/RMG/CMapGenerator.cpp @@ -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 & players, int randomSeed) : + mapGenOptions(mapGenOptions), randomSeed(randomSeed), players(players) +{ + gen.seed(randomSeed); + validateOptions(); +} + +CMapGenerator::~CMapGenerator() +{ + +} + +std::unique_ptr CMapGenerator::generate() +{ + finalizeMapGenOptions(); + + //TODO select a template based on the map gen options or adapt it if necessary + + map = std::unique_ptr(new CMap()); + addHeaderInfo(); + + terViewPatternConfig = std::unique_ptr(new CTerrainViewPatternConfig()); + mapMgr = std::unique_ptr(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(gen.getInteger(0, 2))); + } + if(mapGenOptions.getMonsterStrength() == EMonsterStrength::RANDOM) + { + mapGenOptions.setMonsterStrength(static_cast(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 , "; + ss << "Random seed was " << randomSeed << ", size " << map->width << "x"; + ss << map->height << ", levels " << (map->twoLevel ? "2" : "1") << ", "; + ss << "humans " << static_cast(mapGenOptions.getPlayersCnt()) << ", computers "; + ss << static_cast(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, 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(std::count_if(players.begin(), players.end(), [](const std::pair & 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(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; +} diff --git a/lib/RMG/CMapGenerator.h b/lib/RMG/CMapGenerator.h new file mode 100644 index 000000000..af9597c60 --- /dev/null +++ b/lib/RMG/CMapGenerator.h @@ -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 & players, int randomSeed); + + /** + * Destructor. + */ + ~CMapGenerator(); + + /** + * Generates a map. + * + * @return the generated map object stored in a unique ptr + */ + std::unique_ptr 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 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 terViewPatternConfig; + + /** The map edit manager. */ + std::unique_ptr mapMgr; + + /** The random gen player settings. */ + std::map players; + + /** Typedef of the players map, so that boost foreach can be used. */ + typedef std::map tPlayersMap; +}; diff --git a/lib/StartInfo.h b/lib/StartInfo.h index ab4ca422d..923651b1c 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -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 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; + } }; diff --git a/lib/StringConstants.h b/lib/StringConstants.h index 0122151cb..96efc50c5 100644 --- a/lib/StringConstants.h +++ b/lib/StringConstants.h @@ -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 diff --git a/lib/VCMI_lib.vcxproj b/lib/VCMI_lib.vcxproj index 4cca92291..81cd40e15 100644 --- a/lib/VCMI_lib.vcxproj +++ b/lib/VCMI_lib.vcxproj @@ -197,7 +197,9 @@ + + @@ -239,6 +241,7 @@ + @@ -258,7 +261,9 @@ + +