diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index 3e9b6a65b..37ad79386 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -3,9 +3,9 @@ #include "VCAI.h" #include "../../lib/UnlockGuard.h" -#include "../../lib/CObjectHandler.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CHeroHandler.h" +#include "../../lib/mapObjects/CBank.h" /* * AIUtility.cpp, part of VCMI engine @@ -303,11 +303,11 @@ ui64 evaluateDanger(const CGObjectInstance *obj) case Obj::SHIPWRECK: //shipwreck case Obj::DERELICT_SHIP: //derelict ship // case Obj::PYRAMID: - return fh->estimateBankDanger (VLC->objh->bankObjToIndex(obj)); + return fh->estimateBankDanger (dynamic_cast(obj)); case Obj::PYRAMID: { if(obj->subID == 0) - return fh->estimateBankDanger (VLC->objh->bankObjToIndex(obj)); + return fh->estimateBankDanger (dynamic_cast(obj)); else return 0; } @@ -377,7 +377,7 @@ int3 whereToExplore(HeroPtr h) } } } - removeDuplicates (nearbyVisitableObjs); //one object may occupy multiple tiles + vstd::removeDuplicates (nearbyVisitableObjs); //one object may occupy multiple tiles boost::sort(nearbyVisitableObjs, isCloser); if(nearbyVisitableObjs.size()) return nearbyVisitableObjs.back()->visitablePos(); diff --git a/AI/VCAI/AIUtility.h b/AI/VCAI/AIUtility.h index 29e14c63f..91116d890 100644 --- a/AI/VCAI/AIUtility.h +++ b/AI/VCAI/AIUtility.h @@ -5,7 +5,6 @@ #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" #include "../../lib/CSpellHandler.h" -#include "../../lib/CObjectHandler.h" #include "../../lib/Connection.h" #include "../../lib/CGameState.h" #include "../../lib/mapping/CMap.h" @@ -135,44 +134,6 @@ bool objWithID(const CGObjectInstance *obj) return obj->ID == id; } -template -bool erase_if_present(Container &c, const Item &item) -{ - auto i = std::find(c.begin(), c.end(), item); - if (i != c.end()) - { - c.erase(i); - return true; - } - - return false; -} - -template -bool erase_if_present(std::map & c, const Item2 &item) -{ - auto i = c.find(item); - if (i != c.end()) - { - c.erase(i); - return true; - } - return false; -} - -template -void erase(Container &c, Pred pred) -{ - c.erase(boost::remove_if(c, pred), c.end()); -} - -template -void removeDuplicates(std::vector &vec) -{ - boost::sort(vec); - vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); -} - std::string strFromInt3(int3 pos); void foreach_tile_pos(std::function foo); void foreach_tile_pos(CCallback * cbp, std::function foo); // avoid costly retrieval of thread-specific pointer @@ -200,4 +161,4 @@ bool compareMovement(HeroPtr lhs, HeroPtr rhs); bool compareHeroStrength(HeroPtr h1, HeroPtr h2); bool compareArmyStrength(const CArmedInstance *a1, const CArmedInstance *a2); ui64 howManyReinforcementsCanGet(HeroPtr h, const CGTownInstance *t); -int3 whereToExplore(HeroPtr h); \ No newline at end of file +int3 whereToExplore(HeroPtr h); diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 4c8fe068f..db173016b 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -2,11 +2,11 @@ #include "Fuzzy.h" #include -#include "../../lib/CObjectHandler.h" +#include "../../lib/mapObjects/MapObjects.h" +#include "../../lib/mapObjects/CommonConstructors.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/VCMI_Lib.h" #include "../../CCallback.h" -//#include "Goals.cpp" #include "VCAI.h" /* @@ -42,16 +42,6 @@ struct armyStructure ui32 maxSpeed; }; -ui64 evaluateBankConfig (BankConfig * bc) -{ - ui64 danger = 0; - for (auto opt : bc->guards) - { - danger += VLC->creh->creatures[opt.first]->fightValue * opt.second; - } - return danger; -} - armyStructure evaluateArmyStructure (const CArmedInstance * army) { ui64 totalStrenght = army->getArmyStrength(); @@ -209,42 +199,26 @@ void FuzzyHelper::initTacticalAdvantage() } } -ui64 FuzzyHelper::estimateBankDanger (int ID) +ui64 FuzzyHelper::estimateBankDanger (const CBank * bank) { - std::vector > & configs = VLC->objh->banksInfo[ID]; + auto info = VLC->objtypeh->getHandlerFor(bank->ID, bank->subID)->getObjectInfo(bank->appearance); + ui64 val = std::numeric_limits::max(); try { - switch (configs.size()) - { - case 4: - try - { - for (int i = 0; i < 4; ++i) - { - int bankVal = evaluateBankConfig (VLC->objh->banksInfo[ID][i]); - bankDanger->term("Bank" + boost::lexical_cast(i))->setMinimum(bankVal * 0.5f); - bankDanger->term("Bank" + boost::lexical_cast(i))->setMaximum(bankVal * 1.5f); - } - //comparison purposes - //int averageValue = (evaluateBankConfig (VLC->objh->banksInfo[ID][0]) + evaluateBankConfig (VLC->objh->banksInfo[ID][3])) * 0.5; - //dynamic_cast(bankInput->term("SET"))->setValue(0.5); - bankInput->setInput (0.5); - engine.process (BANK_DANGER); - //engine.process(); - val = bankDanger->output().defuzzify(); //some expected value of this bank - } - catch (fl::FuzzyException & fe) - { - logAi->errorStream() << fe.name() << ": " << fe.message(); - } - break; - case 1: //rare case - Pyramid - val = evaluateBankConfig (VLC->objh->banksInfo[ID][0]); - break; - default: - logAi->warnStream() << ("Uhnandled bank config!"); - } + bankDanger->term("Bank0")->setMinimum(info->minGuards().totalStrength * 0.5f); + bankDanger->term("Bank0")->setMaximum(info->minGuards().totalStrength * 1.5f); + + bankDanger->term("Bank1")->setMinimum(info->maxGuards().totalStrength * 0.5f); + bankDanger->term("Bank1")->setMaximum(info->maxGuards().totalStrength * 1.5f); + + //comparison purposes + //int averageValue = (evaluateBankConfig (VLC->objh->banksInfo[ID][0]) + evaluateBankConfig (VLC->objh->banksInfo[ID][3])) * 0.5; + //dynamic_cast(bankInput->term("SET"))->setValue(0.5); + bankInput->setInput (0.5); + engine.process (BANK_DANGER); + //engine.process(); + val = bankDanger->output().defuzzify(); //some expected value of this bank } catch (fl::FuzzyException & fe) { diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index 5d9a28ab9..c81bb5569 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -14,6 +14,7 @@ class VCAI; class CArmedInstance; +class CBank; class FuzzyHelper { @@ -72,7 +73,7 @@ public: float evaluate (Goals::AbstractGoal & g); void setPriority (Goals::TSubgoal & g); - ui64 estimateBankDanger (int ID); + ui64 estimateBankDanger (const CBank * bank); float getTacticalAdvantage (const CArmedInstance *we, const CArmedInstance *enemy); //returns factor how many times enemy is stronger than us Goals::TSubgoal chooseSolution (Goals::TGoalVec vec); diff --git a/AI/VCAI/Goals.h b/AI/VCAI/Goals.h index 096b63283..de8824906 100644 --- a/AI/VCAI/Goals.h +++ b/AI/VCAI/Goals.h @@ -1,7 +1,6 @@ #pragma once #include "../../lib/VCMI_Lib.h" -#include "../../lib/CObjectHandler.h" #include "../../lib/CBuildingHandler.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 598070edf..ba857730b 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -2,7 +2,7 @@ #include "VCAI.h" #include "Goals.h" #include "../../lib/UnlockGuard.h" -#include "../../lib/CObjectHandler.h" +#include "../../lib/mapObjects/MapObjects.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CHeroHandler.h" diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 17d0843be..b04d7fffc 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -4,7 +4,6 @@ #include "Goals.h" #include "../../lib/AI_Base.h" #include "../../CCallback.h" -#include "../../lib/CDefObjInfoHandler.h" #include "../../lib/CThreadHelper.h" @@ -14,7 +13,6 @@ #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" #include "../../lib/CSpellHandler.h" -#include "../../lib/CObjectHandler.h" #include "../../lib/Connection.h" #include "../../lib/CGameState.h" #include "../../lib/mapping/CMap.h" diff --git a/CCallback.cpp b/CCallback.cpp index fb5e807d2..d3ee913a6 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -9,10 +9,9 @@ #include "client/Client.h" #include "lib/mapping/CMap.h" #include "lib/CBuildingHandler.h" -#include "lib/CDefObjInfoHandler.h" +#include "lib/mapObjects/CObjectClassesHandler.h" #include "lib/CGeneralTextHandler.h" #include "lib/CHeroHandler.h" -#include "lib/CObjectHandler.h" #include "lib/Connection.h" #include "lib/NetPacks.h" #include "client/mapHandler.h" diff --git a/CMakeLists.txt b/CMakeLists.txt index e848deff7..8e92f75aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,9 +117,14 @@ endif() if(CMAKE_COMPILER_IS_GNUCXX OR NOT WIN32) #so far all *nix compilers support such parameters if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set(CLANG_SPECIFIC_FLAGS "-Wno-mismatched-tags") + set(CLANG_SPECIFIC_FLAGS "-Wno-mismatched-tags") endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall -Wextra -Wpointer-arith -Wno-switch -Wno-sign-compare -Wno-unused-parameter -Wuninitialized -Wno-overloaded-virtual ${CLANG_SPECIFIC_FLAGS}") + + if(UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") + endif() + endif() if(WIN32) # on Win everything goes into H3 root directory diff --git a/Global.h b/Global.h index 3802796ef..a9c81fe59 100644 --- a/Global.h +++ b/Global.h @@ -608,6 +608,44 @@ namespace vstd return defaultValue; } + template + bool erase_if_present(Container &c, const Item &item) + { + auto i = std::find(c.begin(), c.end(), item); + if (i != c.end()) + { + c.erase(i); + return true; + } + + return false; + } + + template + bool erase_if_present(std::map & c, const Item2 &item) + { + auto i = c.find(item); + if (i != c.end()) + { + c.erase(i); + return true; + } + return false; + } + + template + void erase(Container &c, Pred pred) + { + c.erase(boost::remove_if(c, pred), c.end()); + } + + template + void removeDuplicates(std::vector &vec) + { + boost::sort(vec); + vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); + } + using boost::math::round; } using vstd::operator-=; diff --git a/client/AdventureMapClasses.cpp b/client/AdventureMapClasses.cpp index e8774e695..648a3222c 100644 --- a/client/AdventureMapClasses.cpp +++ b/client/AdventureMapClasses.cpp @@ -6,7 +6,7 @@ #include "../lib/filesystem/Filesystem.h" #include "../lib/mapping/CMap.h" #include "../lib/CModHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/CGameState.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" diff --git a/client/CAdvmapInterface.cpp b/client/CAdvmapInterface.cpp index e2fab0dcc..e6e44d561 100644 --- a/client/CAdvmapInterface.cpp +++ b/client/CAdvmapInterface.cpp @@ -17,7 +17,7 @@ #include "CDefHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/CTownHandler.h" #include "../lib/mapping/CMap.h" #include "../lib/JsonNode.h" @@ -25,7 +25,7 @@ #include "CPreGame.h" #include "../lib/VCMI_Lib.h" #include "../lib/CSpellHandler.h" -#include "CSoundBase.h" +#include "../lib/CSoundBase.h" #include "../lib/CGameState.h" #include "CMusicHandler.h" #include "gui/CGuiHandler.h" diff --git a/client/CCastleInterface.cpp b/client/CCastleInterface.cpp index 1a3759986..394479ca1 100644 --- a/client/CCastleInterface.cpp +++ b/client/CCastleInterface.cpp @@ -7,7 +7,7 @@ #include "../lib/CCreatureHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CModHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/CSpellHandler.h" #include "../lib/CTownHandler.h" #include "CAdvmapInterface.h" diff --git a/client/CGameInfo.cpp b/client/CGameInfo.cpp index 06786663f..cf2f596f0 100644 --- a/client/CGameInfo.cpp +++ b/client/CGameInfo.cpp @@ -31,5 +31,5 @@ void CGameInfo::setFromLib() heroh = VLC->heroh; objh = VLC->objh; spellh = VLC->spellh; - dobjinfo = VLC->dobjinfo; + objtypeh = VLC->objtypeh; } diff --git a/client/CGameInfo.h b/client/CGameInfo.h index 4ea111a62..d0393568e 100644 --- a/client/CGameInfo.h +++ b/client/CGameInfo.h @@ -23,7 +23,7 @@ class CBuildingHandler; class CObjectHandler; class CSoundHandler; class CMusicHandler; -class CDefObjInfoHandler; +class CObjectClassesHandler; class CTownHandler; class CGeneralTextHandler; class CConsoleHandler; @@ -57,11 +57,10 @@ public: ConstTransitivePtr creh; ConstTransitivePtr spellh; ConstTransitivePtr objh; - ConstTransitivePtr dobjinfo; + ConstTransitivePtr objtypeh; CGeneralTextHandler * generaltexth; CMapHandler * mh; CTownHandler * townh; - //CTownHandler * townh; void setFromLib(); diff --git a/client/CHeroWindow.cpp b/client/CHeroWindow.cpp index 721c8da0a..3d04917d9 100644 --- a/client/CHeroWindow.cpp +++ b/client/CHeroWindow.cpp @@ -20,7 +20,7 @@ #include "CDefHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/NetPacksBase.h" #include "gui/CGuiHandler.h" diff --git a/client/CHeroWindow.h b/client/CHeroWindow.h index d23443f64..3d922d0c4 100644 --- a/client/CHeroWindow.h +++ b/client/CHeroWindow.h @@ -4,8 +4,6 @@ #include "gui/CIntObjectClasses.h" #include "GUIClasses.h" -//#include "CPlayerInterface.h" - /* * CHeroWindow.h, part of VCMI engine * diff --git a/client/CKingdomInterface.cpp b/client/CKingdomInterface.cpp index 4d62e8523..83f730515 100644 --- a/client/CKingdomInterface.cpp +++ b/client/CKingdomInterface.cpp @@ -5,7 +5,7 @@ #include "../lib/CCreatureHandler.h" //creatures name for objects list #include "../lib/CGeneralTextHandler.h" #include "../lib/CModHandler.h" //for buildings per turn -#include "../lib/CObjectHandler.h" //Hero/Town objects +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/CHeroHandler.h" // only for calculating required xp? worth it? #include "../lib/CTownHandler.h" #include "CAnimation.h" //CAnimImage @@ -500,7 +500,7 @@ void CKingdomInterface::generateObjectsList(const std::vectorsubID]; if (info.count++ == 0) { - info.hoverText = CGI->creh->creatures[CGI->objh->cregens.find(object->subID)->second]->namePl; + info.hoverText = object->getHoverText(); info.imageID = object->subID; } } @@ -953,4 +953,4 @@ void CHeroItem::onArtChange(int tabIndex) //redraw item after background change if (active) redraw(); -} \ No newline at end of file +} diff --git a/client/CMT.cpp b/client/CMT.cpp index 56659b584..7de8eed78 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -34,7 +34,6 @@ #include "CMessage.h" #include "../lib/CModHandler.h" #include "../lib/CTownHandler.h" -#include "../lib/CObjectHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CScriptingModule.h" #include "../lib/GameConstants.h" @@ -44,7 +43,6 @@ #ifdef _WIN32 #include "SDL_syswm.h" #endif -#include "../lib/CDefObjInfoHandler.h" #include "../lib/UnlockGuard.h" #include "CMT.h" diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 9f78671ce..b963a530c 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -48,8 +48,6 @@ set(client_SRCS ) set(client_HEADERS - CSoundBase.h - gui/SDL_Pixels.h ) diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index 40ad98247..ae583235d 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -2,9 +2,9 @@ #include #include "CMusicHandler.h" +#include "CGameInfo.h" #include "../lib/CCreatureHandler.h" #include "../lib/CSpellHandler.h" -#include "../client/CGameInfo.h" #include "../lib/JsonNode.h" #include "../lib/GameConstants.h" #include "../lib/filesystem/Filesystem.h" diff --git a/client/CMusicHandler.h b/client/CMusicHandler.h index 2f77deca1..d5f87981c 100644 --- a/client/CMusicHandler.h +++ b/client/CMusicHandler.h @@ -1,7 +1,7 @@ #pragma once #include "../lib/CConfigHandler.h" -#include "CSoundBase.h" +#include "../lib/CSoundBase.h" #include "../lib/CCreatureHandler.h" /* diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 9857b1e5c..a022ac1c4 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1,5 +1,4 @@ #include "StdInc.h" -#include "../lib/CDefObjInfoHandler.h" #include "CAdvmapInterface.h" #include "battle/CBattleInterface.h" #include "battle/CBattleInterfaceClasses.h" @@ -13,7 +12,6 @@ #include "CQuestLog.h" #include "CMessage.h" #include "CPlayerInterface.h" -//#include "gui/SDL_Extensions.h" #include "gui/SDL_Extensions.h" #include "../lib/CConfigHandler.h" #include "battle/CCreatureAnimation.h" @@ -21,7 +19,6 @@ #include "../lib/CArtHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CObjectHandler.h" #include "../lib/Connection.h" #include "../lib/CSpellHandler.h" #include "../lib/CTownHandler.h" diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index 61bc3651d..020030195 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -11,11 +11,9 @@ #include "gui/CCursorHandler.h" #include "CAnimation.h" #include "CDefHandler.h" -#include "../lib/CDefObjInfoHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CObjectHandler.h" #include "../lib/mapping/CCampaignHandler.h" #include "../lib/CCreatureHandler.h" #include "../lib/JsonNode.h" @@ -1716,7 +1714,10 @@ CRandomMapTab::CRandomMapTab() addButtonsWithRandToGroup(monsterStrengthGroup, monsterStrengthBtns, 0, 2, WIDE_BTN_WIDTH, 248, 251); monsterStrengthGroup->onChange = [&](int btnId) { - mapGenOptions.setMonsterStrength(static_cast(btnId)); + if (btnId < 0) + mapGenOptions.setMonsterStrength(EMonsterStrength::RANDOM); + else + mapGenOptions.setMonsterStrength(static_cast(btnId + EMonsterStrength::GLOBAL_WEAK)); //value 2 to 4 }; // Show random maps btn diff --git a/client/CQuestLog.cpp b/client/CQuestLog.cpp index e573c761a..1eb621ce4 100644 --- a/client/CQuestLog.cpp +++ b/client/CQuestLog.cpp @@ -16,7 +16,6 @@ #include "../lib/CGameState.h" #include "../lib/CArtHandler.h" #include "../lib/NetPacksBase.h" -#include "../lib/CObjectHandler.h" #include "gui/CGuiHandler.h" #include "gui/CIntObjectClasses.h" diff --git a/client/CSpellWindow.cpp b/client/CSpellWindow.cpp index 1f88df9e2..36c269967 100644 --- a/client/CSpellWindow.cpp +++ b/client/CSpellWindow.cpp @@ -3,7 +3,6 @@ #include "Graphics.h" #include "CDefHandler.h" -#include "../lib/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CGeneralTextHandler.h" #include "CVideoHandler.h" diff --git a/client/Client.cpp b/client/Client.cpp index 1db969f2f..528895cfd 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -11,11 +11,9 @@ #include "../lib/BattleState.h" #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" -#include "../lib/CDefObjInfoHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" -#include "../lib/CObjectHandler.h" #include "../lib/CBuildingHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/Connection.h" diff --git a/client/GUIClasses.cpp b/client/GUIClasses.cpp index bb54c4f3c..1d2b8fa91 100644 --- a/client/GUIClasses.cpp +++ b/client/GUIClasses.cpp @@ -24,7 +24,6 @@ #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CModHandler.h" -#include "../lib/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CTownHandler.h" #include "../lib/CondSh.h" diff --git a/client/Graphics.cpp b/client/Graphics.cpp index 1daac75ed..54be6becd 100644 --- a/client/Graphics.cpp +++ b/client/Graphics.cpp @@ -12,19 +12,17 @@ #include "../CCallback.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" -#include "../lib/CObjectHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CCreatureHandler.h" #include "CBitmapHandler.h" -#include "../lib/CObjectHandler.h" #include "../lib/CSpellHandler.h" -#include "../lib/CDefObjInfoHandler.h" #include "../lib/CGameState.h" #include "../lib/JsonNode.h" #include "../lib/vcmi_endian.h" #include "../lib/GameConstants.h" #include "../lib/CStopWatch.h" #include "CAnimation.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" using namespace boost::assign; using namespace CSDL_Ext; @@ -166,13 +164,11 @@ void Graphics::loadHeroAnims() for(auto & elem : CGI->heroh->classes.heroClasses) { - const CHeroClass * hc = elem; - - if (!vstd::contains(heroAnims, hc->imageMapFemale)) - heroAnims[hc->imageMapFemale] = loadHeroAnim(hc->imageMapFemale, rotations); - - if (!vstd::contains(heroAnims, hc->imageMapMale)) - heroAnims[hc->imageMapMale] = loadHeroAnim(hc->imageMapMale, rotations); + for (auto & templ : VLC->objtypeh->getHandlerFor(Obj::HERO, elem->id)->getTemplates()) + { + if (!heroAnims.count(templ.animationFile)) + heroAnims[templ.animationFile] = loadHeroAnim(templ.animationFile, rotations); + } } boatAnims.push_back(loadHeroAnim("AB01_.DEF", rotations)); diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 6c928f893..7f6f5028c 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -9,14 +9,12 @@ #include "CGameInfo.h" #include "../lib/Connection.h" #include "../lib/CGeneralTextHandler.h" -#include "../lib/CDefObjInfoHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CObjectHandler.h" #include "../lib/VCMI_Lib.h" #include "../lib/mapping/CMap.h" #include "../lib/VCMIDirs.h" #include "../lib/CSpellHandler.h" -#include "CSoundBase.h" +#include "../lib/CSoundBase.h" #include "mapHandler.h" #include "GUIClasses.h" #include "../lib/CConfigHandler.h" diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 54b1375e1..5179ce3b8 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -6,7 +6,6 @@ #include "../CAdvmapInterface.h" #include "../CAnimation.h" #include "../CBitmapHandler.h" -#include "../../lib/CObjectHandler.h" #include "../../lib/CHeroHandler.h" # include "../CDefHandler.h" #include "../../lib/CSpellHandler.h" diff --git a/client/battle/CBattleInterfaceClasses.cpp b/client/battle/CBattleInterfaceClasses.cpp index 9e45f198b..fcf3d878e 100644 --- a/client/battle/CBattleInterfaceClasses.cpp +++ b/client/battle/CBattleInterfaceClasses.cpp @@ -16,7 +16,6 @@ #include "../../lib/CGeneralTextHandler.h" #include "../../lib/NetPacks.h" #include "../../lib/CCreatureHandler.h" -#include "../../lib/CObjectHandler.h" #include "../../lib/BattleState.h" #include "../CMusicHandler.h" #include "../CVideoHandler.h" diff --git a/client/battle/CCreatureAnimation.h b/client/battle/CCreatureAnimation.h index bae803d51..f8fb967f0 100644 --- a/client/battle/CCreatureAnimation.h +++ b/client/battle/CCreatureAnimation.h @@ -1,7 +1,6 @@ #pragma once #include "../../lib/FunctionList.h" -//#include "../CDefHandler.h" #include "../CAnimation.h" /* diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 3f47f7a0b..7c7ac0575 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -14,12 +14,12 @@ #include "CBitmapHandler.h" #include "gui/SDL_Extensions.h" #include "CGameInfo.h" -#include "../lib/CDefObjInfoHandler.h" +#include "../lib/mapObjects/CGHeroInstance.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGameState.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" #include "Graphics.h" -#include "../lib/CObjectHandler.h" #include "../lib/mapping/CMap.h" #include "CDefHandler.h" #include "../lib/CConfigHandler.h" @@ -207,7 +207,7 @@ void CMapHandler::borderAndTerrainBitmapInit() terBitmapNum = 17; else if(i==(sizes.x) && j==(sizes.y)) terBitmapNum = 18; - else if(j == -1 && i > -1 && i < sizes.y) + else if(j == -1 && i > -1 && i < sizes.x) terBitmapNum = rand.nextInt(22, 23); else if(i == -1 && j > -1 && j < sizes.y) terBitmapNum = rand.nextInt(33, 34); @@ -541,10 +541,8 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std:: //pick graphics of hero (or boat if hero is sailing) if (themp->boat) iv = &graphics->boatAnims[themp->boat->subID]->ourImages; - else if (themp->sex) - iv = &graphics->heroAnims[themp->type->heroClass->imageMapFemale]->ourImages; else - iv = &graphics->heroAnims[themp->type->heroClass->imageMapMale]->ourImages; + iv = &graphics->heroAnims[themp->appearance.animationFile]->ourImages; //pick appropriate flag set if(themp->boat) @@ -1073,7 +1071,7 @@ void CMapHandler::getTerrainDescr( const int3 &pos, std::string & out, bool terN } if(t.hasFavourableWinds()) - out = CGI->generaltexth->names[Obj::FAVORABLE_WINDS]; + out = CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS); else if(terName) out = CGI->generaltexth->terrainNames[t.terType]; } diff --git a/config/bankconfig.json b/config/bankconfig.json index d610606cd..778bd2f43 100644 --- a/config/bankconfig.json +++ b/config/bankconfig.json @@ -1,675 +1,9 @@ //Resources: Wood, Mercury, Ore, Sulfur, Crystal, Gems, Gold //Artifacts: Treasure, Minor, Major, Relic +//NOTE: all H3M banks were moved to objects/creatureBanks.json +//Remaining part should be moved to WoG mod { "banks": [ - { - "name" : "Cyclops Stockpile", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 20, "id": 94 } ], - "upgrade_chance": 50, - "combat_value": 506, - "reward_resources": - { - "wood" : 4, - "mercury" : 4, - "ore" : 4, - "sulfur" : 4, - "crystal" : 4, - "gems" : 4, - "gold" : 0 - }, - "value": 10000, - "profitability": 20, - "easiest": 100 - }, - - { - "chance": 30, - "guards": [ { "number": 30, "id": 94 } ], - "upgrade_chance": 50, - "combat_value": 760, - "reward_resources": - { - "wood" : 6, - "mercury" : 6, - "ore" : 6, - "sulfur" : 6, - "crystal" : 6, - "gems" : 6 - }, - "value": 15000, - "profitability": 20, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 40, "id": 94 } ], - "upgrade_chance": 50, - "combat_value": 1013, - "reward_resources": - { - "wood" : 8, - "mercury" : 8, - "ore" : 8, - "sulfur" : 8, - "crystal" : 8, - "gems" : 8 - }, - "value": 20000, - "profitability": 20, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 50, "id": 94 } ], - "upgrade_chance": 50, - "combat_value": 1266, - "reward_resources": - { - "wood" : 10, - "mercury" : 10, - "ore" : 10, - "sulfur" : 10, - "crystal" : 10, - "gems" : 10 - }, - "value": 25000, - "profitability": 20, - "easiest": 250 - } - ] - }, - - { - "name" : "Dwarven Treasury", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 50, "id": 16 } ], - "upgrade_chance": 50, - "combat_value": 194, - "reward_resources": - { - "crystal" : 2, - "gold" : 2500 - }, - "value": 3500, - "profitability": 18, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 75, "id": 16 } ], - "upgrade_chance": 50, - "combat_value": 291, - "reward_resources": - { - "crystal" : 3, - "gold" : 4000 - }, - "value": 5500, - "profitability": 19, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 100, "id": 16 } ], - "upgrade_chance": 50, - "combat_value": 388, - "reward_resources": - { - "crystal" : 5, - "gold" : 5000 - }, - "value": 7500, - "profitability": 19, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 150, "id": 16 } ], - "upgrade_chance": 50, - "combat_value": 582, - "reward_resources": - { - "crystal" : 10, - "gold" : 7500 - }, - "value": 12500, - "profitability": 21, - "easiest": 300 - } - ] - }, - - { - "name" : "Griffin Conservatory", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 50, "id": 4 } ], - "upgrade_chance": 50, - "combat_value": 351, - "reward_creatures": [ { "number": 1, "id": 12 } ], - "value": 3000, - "profitability": 9, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 100, "id": 4 } ], - "upgrade_chance": 50, - "combat_value": 702, - "reward_creatures": [ { "number": 2, "id": 12 } ], - "value": 6000, - "profitability": 9, - "easiest": 200 - }, - { - "chance": 30, - "guards": [ { "number": 150, "id": 4 } ], - "upgrade_chance": 50, - "combat_value": 1053, - "reward_creatures": [ { "number": 3, "id": 12 } ], - "value": 9000, - "profitability": 9, - "easiest": 300 - }, - { - "chance": 10, - "guards": [ { "number": 200, "id": 4 } ], - "upgrade_chance": 50, - "combat_value": 1404, - "reward_creatures": [ { "number": 4, "id": 12 } ], - "value": 12000, - "profitability": 9, - "easiest": 400 - } - ] - }, - - { - "name" : "Imp Cache", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 100, "id": 42 } ], - "upgrade_chance": 50, - "combat_value": 100, - "reward_resources": - { - "wood" : 0, - "mercury" : 2, - "ore" : 0, - "sulfur" : 0, - "crystal" : 0, - "gems" : 0, - "gold" : 1000 - }, - "value": 2000, - "profitability": 20, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 150, "id": 42 } ], - "upgrade_chance": 50, - "combat_value": 150, - "reward_resources": - { - "mercury" : 3, - "gold" : 1500 - }, - "value": 3000, - "profitability": 20, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 200, "id": 42 } ], - "upgrade_chance": 50, - "combat_value": 200, - "reward_resources": - { - "mercury" : 4, - "gold" : 2000 - }, - "value": 4000, - "profitability": 20, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 300, "id": 42 } ], - "upgrade_chance": 50, - "combat_value": 300, - "reward_resources": - { - "mercury" : 6, - "gold" : 3000 - }, - "value": 6000, - "profitability": 20, - "easiest": 300 - } - ] - }, - - { - "name" : "Medusa Stores", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 20, "id": 76 } ], - "upgrade_chance": 50, - "combat_value": 207, - "reward_resources": - { - "sulfur" : 5, - "gold" : 2000 - }, - "value": 4500, - "profitability": 22, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 30, "id": 76 } ], - "upgrade_chance": 50, - "combat_value": 310, - "reward_resources": - { - "sulfur" : 6, - "gold" : 3000 - }, - "value": 6000, - "profitability": 19, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 40, "id": 76 } ], - "upgrade_chance": 50, - "combat_value": 414, - "reward_resources": - { - "sulfur" : 8, - "gold" : 4000 - }, - "value": 8000, - "profitability": 19, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 50, "id": 76 } ], - "upgrade_chance": 50, - "combat_value": 517, - "reward_resources": - { - "sulfur" : 10, - "gold" : 5000 - }, - "value": 10000, - "profitability": 19, - "easiest": 250 - } - ] - }, - - { - "name" : "Naga Bank", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 10, "id": 38 } ], - "upgrade_chance": 50, - "combat_value": 403, - "reward_resources": - { - "gems" : 8, - "gold" : 4000 - }, - "value": 8000, - "profitability": 20, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 15, "id": 38 } ], - "upgrade_chance": 50, - "combat_value": 605, - "reward_resources": - { - "gems" : 12, - "gold" : 6000 - }, - "value": 12000, - "profitability": 20, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 20, "id": 38 } ], - "upgrade_chance": 50, - "combat_value": 806, - "reward_resources": - { - "gems" : 16, - "gold" : 8000 - }, - "value": 16000, - "profitability": 20, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 30, "id": 38 } ], - "upgrade_chance": 50, - "combat_value": 1210, - "reward_resources": - { - "gems" : 24, - "gold" : 12000 - }, - "value": 24000, - "profitability": 20, - "easiest": 300 - } - ] - }, - - { - "name" : "Dragon Fly Hive", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 30, "id": 105} ], - "upgrade_chance": 0, - "combat_value": 154, - "reward_creatures": [ { "number": 4, "id": 108 } ], - "value": 3200, - "profitability": 21, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 45, "id": 105 } ], - "upgrade_chance": 0, - "combat_value": 230, - "reward_creatures": [ { "number": 6, "id": 108 } ], - "value": 4800, - "profitability": 21, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 60, "id": 105 } ], - "upgrade_chance": 0, - "combat_value": 307, - "reward_creatures": [ { "number": 8, "id": 108 } ], - "value": 6400, - "profitability": 21, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 90, "id": 105 } ], - "upgrade_chance": 0, - "combat_value": 461, - "reward_creatures": [ { "number": 12, "id": 108 } ], - "value": 9600, - "profitability": 21, - "easiest": 300 - } - ] - }, - - { - "name" : "Shipwreck", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 10, "id": 60 } ], - "upgrade_chance": 0, - "combat_value": 31, - "reward_resources": - { - "gold" : 2000 - }, - "value": 2000, - "profitability": 65, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 15, "id": 60 } ], - "upgrade_chance": 0, - "combat_value": 46, - "reward_resources": - { - "gold" : 3000 - }, - "value": 3000, - "profitability": 65, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 25, "id": 60 } ], - "upgrade_chance": 0, - "combat_value": 77, - "reward_resources": - { - "gold" : 4000 - }, - "reward_artifacts": [ 1, 0, 0, 0 ], - "value": 5000, - "profitability": 65, - "easiest": 250 - }, - { - "chance": 10, - "guards": [ { "number": 50, "id": 60 } ], - "upgrade_chance": 0, - "combat_value": 154, - "reward_resources": - { - "gold" : 5000 - }, - "reward_artifacts": [ 0, 1, 0, 0 ], - "value": 7000, - "profitability": 45, - "easiest": 500 - } - ] - }, - - { - "name" : "Derelict Ship", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 20, "id": 115 } ], - "upgrade_chance": 0, - "combat_value": 138, - "reward_resources": - { - "gold" : 3000 - }, - "value": 3000, - "profitability": 22, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 30, "id": 115 } ], - "upgrade_chance": 0, - "combat_value": 207, - "reward_resources": - { - "gold" : 3000 - }, - "reward_artifacts": [ 1, 0, 0, 0 ], - "value": 4000, - "profitability": 19, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 40, "id": 115 } ], - "upgrade_chance": 0, - "combat_value": 276, - "reward_resources": - { - "gold" : 4000 - }, - "reward_artifacts": [ 1, 0, 0, 0 ], - "value": 5000, - "profitability": 18, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 60, "id": 115 } ], - "upgrade_chance": 0, - "combat_value": 414, - "reward_resources": - { - "gold" : 6000 - }, - "reward_artifacts": [ 0, 1, 0, 0 ], - "value": 8000, - "profitability": 19, - "easiest": 300 - } - ] - }, - - { - "name" : "Crypt", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 30, "id": 56 }, { "number": 20, "id": 58 }, { "number": 0, "id": 60 } , { "number": 0, "id": 62 } ], - "upgrade_chance": 0, - "combat_value": 75, - "reward_resources": - { - "gold" : 1500 - }, - "value": 1500, - "profitability": 20, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 25, "id": 56 }, { "number": 20, "id": 58 }, { "number": 5, "id": 60 }, { "number": 0, "id": 62 } ], - "upgrade_chance": 0, - "combat_value": 94, - "reward_resources": - { - "gold" : 2000 - }, - "value": 2000, - "profitability": 21, - "easiest": 126 - }, - { - "chance": 30, - "guards": [ { "number": 20, "id": 56 }, { "number": 20, "id": 58 }, { "number": 10, "id": 60 }, { "number": 5, "id": 62 } ], - "upgrade_chance": 0, - "combat_value": 169, - "reward_resources": - { - "gold" : 2500 - }, - "reward_artifacts": [ 1, 0, 0, 0 ], - "value": 3500, - "profitability": 21, - "easiest": 225 - }, - { - "chance": 10, - "guards": [ { "number": 20, "id": 56 }, { "number": 20, "id": 58 }, { "number": 10, "id": 60 }, { "number": 10, "id": 62 } ], - "upgrade_chance": 0, - "combat_value": 225, - "reward_resources": - { - "gold" : 5000 - }, - "reward_artifacts": [ 1, 0, 0, 0 ], - "value": 6000, - "profitability": 27, - "easiest": 299 - } - ] - }, - - { - "name" : "Dragon Utopia", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 8, "id": 26 }, { "number": 5, "id": 82 }, { "number": 2, "id": 27 }, { "number": 1, "id": 83 } ], - "upgrade_chance": 0, - "combat_value": 769, - "reward_resources": - { - "gold" : 20000 - }, - "reward_artifacts": [ 1, 1, 1, 1 ], - "value": 38000, - "profitability": 21, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 8, "id": 26 }, { "number": 6, "id": 82 }, { "number": 3, "id": 27 }, { "number": 2, "id": 83 } ], - "upgrade_chance": 0, - "combat_value": 209, - "reward_resources": - { - "gold" : 30000 - }, - "reward_artifacts": [ 0, 1, 1, 2 ], - "value": 57000, - "profitability": 26, - "easiest": 125 - }, - { - "chance": 30, - "guards": [ { "number": 8, "id": 26 }, { "number": 6, "id": 82 }, { "number": 4, "id": 27 }, { "number": 3, "id": 83 } ], - "upgrade_chance": 0, - "combat_value": 556, - "reward_resources": - { - "gold" : 40000 - }, - "reward_artifacts": [ 0, 0, 1, 3 ], - "value": 75000, - "profitability": 29, - "easiest": 145 - }, - { - "chance": 10, - "guards": [ { "number": 8, "id": 26 }, { "number": 7, "id": 82 }, { "number": 6, "id": 27 }, { "number": 5, "id": 83 } ], - "upgrade_chance": 0, - "combat_value": 343, - "reward_resources": - { - "gold" : 50000 - }, - "reward_artifacts": [ 0, 0, 0, 4 ], - "value": 90000, - "profitability": 27, - "easiest": 189 - } - ] - }, - { "name" : "Hunting Lodge", "levels": [ @@ -1252,21 +586,6 @@ "easiest": 250 } ] - }, - - { - "name" : "Pyramid", - "levels": [ - { - "chance": 100, - "guards": [ { "number": 20, "id": 116 }, { "number": 10, "id": 117 }, { "number": 20, "id": 116 }, { "number": 10, "id": 117 } ], - "upgrade_chance": 0, - "combat_value": 786, - "value": 15000, - "profitability": 19, - "easiest": 100 - } - ] } ] } diff --git a/config/defaultMods.json b/config/defaultMods.json index 9d1c72120..6413b79af 100644 --- a/config/defaultMods.json +++ b/config/defaultMods.json @@ -8,7 +8,8 @@ "creature" : 150, "faction" : 9, "hero" : 156, - "spell" : 81, + "spell" : 81, + "object" : 256, "mapVersion" : 28 // max supported version, SoD }, diff --git a/config/dwellings.json b/config/dwellings.json deleted file mode 100644 index 192b868e2..000000000 --- a/config/dwellings.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - // Indicate which dwelling produces which creature - // Note that it is 1<->n connection since - // a creature can be produced by more than one dwelling. - "dwellings": [ - { "dwelling": 0, "creature": 106 }, - { "dwelling": 1, "creature": 96 }, - { "dwelling": 2, "creature": 74 }, - { "dwelling": 3, "creature": 66 }, - { "dwelling": 4, "creature": 68 }, - { "dwelling": 5, "creature": 10 }, - { "dwelling": 6, "creature": 14 }, - { "dwelling": 7, "creature": 112 }, - { "dwelling": 8, "creature": 12 }, - { "dwelling": 9, "creature": 94 }, - { "dwelling": 10, "creature": 54 }, - { "dwelling": 11, "creature": 104 }, - { "dwelling": 12, "creature": 16 }, - { "dwelling": 13, "creature": 113 }, - { "dwelling": 14, "creature": 52 }, - { "dwelling": 15, "creature": 18 }, - { "dwelling": 16, "creature": 114 }, - { "dwelling": 17, "creature": 30 }, - { "dwelling": 18, "creature": 36 }, - { "dwelling": 19, "creature": 86 }, - { "dwelling": 20, "creature": 98 }, - { "dwelling": 21, "creature": 84 }, - { "dwelling": 22, "creature": 44 }, - { "dwelling": 23, "creature": 102 }, - { "dwelling": 24, "creature": 26 }, - { "dwelling": 25, "creature": 4 }, - { "dwelling": 26, "creature": 72 }, - { "dwelling": 27, "creature": 46 }, - { "dwelling": 28, "creature": 110 }, - { "dwelling": 29, "creature": 42 }, - { "dwelling": 30, "creature": 100 }, - { "dwelling": 31, "creature": 34 }, - { "dwelling": 32, "creature": 80 }, - { "dwelling": 33, "creature": 76 }, - { "dwelling": 34, "creature": 78 }, - { "dwelling": 35, "creature": 8 }, - { "dwelling": 36, "creature": 38 }, - { "dwelling": 37, "creature": 48 }, - { "dwelling": 38, "creature": 90 }, - { "dwelling": 39, "creature": 88 }, - { "dwelling": 40, "creature": 50 }, - { "dwelling": 41, "creature": 82 }, - { "dwelling": 42, "creature": 92 }, - { "dwelling": 43, "creature": 28 }, - { "dwelling": 44, "creature": 40 }, - { "dwelling": 45, "creature": 22 }, - { "dwelling": 46, "creature": 70 }, - { "dwelling": 47, "creature": 115 }, - { "dwelling": 48, "creature": 60 }, - { "dwelling": 49, "creature": 108 }, - { "dwelling": 50, "creature": 20 }, - { "dwelling": 51, "creature": 24 }, - { "dwelling": 52, "creature": 64 }, - { "dwelling": 53, "creature": 62 }, - { "dwelling": 54, "creature": 56 }, - { "dwelling": 55, "creature": 58 }, - { "dwelling": 56, "creature": 0 }, - { "dwelling": 57, "creature": 2 }, - { "dwelling": 58, "creature": 6 }, - { "dwelling": 59, "creature": 118 }, - { "dwelling": 60, "creature": 120 }, - { "dwelling": 61, "creature": 130 }, - { "dwelling": 62, "creature": 132 }, - { "dwelling": 63, "creature": 133 }, - { "dwelling": 64, "creature": 134 }, - { "dwelling": 65, "creature": 135 }, - { "dwelling": 66, "creature": 136 }, - { "dwelling": 67, "creature": 137 }, - { "dwelling": 68, "creature": 24 }, - { "dwelling": 69, "creature": 112 }, - { "dwelling": 70, "creature": 113 }, - { "dwelling": 71, "creature": 114 }, - { "dwelling": 72, "creature": 115 }, - { "dwelling": 73, "creature": 138 }, - { "dwelling": 74, "creature": 139 }, - { "dwelling": 75, "creature": 140 }, - { "dwelling": 76, "creature": 141 }, - { "dwelling": 77, "creature": 142 }, - { "dwelling": 78, "creature": 143 }, - { "dwelling": 79, "creature": 144 }, - { "dwelling": 80, "creature": 150 }, - { "dwelling": 81, "creature": 151 }, - { "dwelling": 82, "creature": 152 }, - { "dwelling": 83, "creature": 153 }, - { "dwelling": 84, "creature": 154 }, - { "dwelling": 85, "creature": 155 }, - { "dwelling": 86, "creature": 156 }, - { "dwelling": 87, "creature": 157 }, - { "dwelling": 88, "creature": 158 }, - { "dwelling": 89, "creature": 171 }, - { "dwelling": 90, "creature": 170 }, - { "dwelling": 91, "creature": 168 }, - { "dwelling": 92, "creature": 172 }, - { "dwelling": 93, "creature": 164 }, - { "dwelling": 94, "creature": 169 }, - { "dwelling": 95, "creature": 173 }, - { "dwelling": 96, "creature": 192 }, - { "dwelling": 97, "creature": 193 }, - { "dwelling": 98, "creature": 194 }, - { "dwelling": 99, "creature": 195 }, - { "dwelling": 100, "creature": 196 } - ] -} diff --git a/config/factions/castle.json b/config/factions/castle.json index 0c3cfba91..1ce63d763 100644 --- a/config/factions/castle.json +++ b/config/factions/castle.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCcasx0.def", - "village" : "AVCCAST0.DEF", - "capitol" : "AVCCASZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCcasx0.def" }, + "village" : { "animation" : "AVCCAST0.DEF" }, + "capitol" : { "animation" : "AVCCASZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/conflux.json b/config/factions/conflux.json index 1d787b4cd..b34e83a29 100644 --- a/config/factions/conflux.json +++ b/config/factions/conflux.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "avchforx.def", - "village" : "AVCHFOR0.DEF", - "capitol" : "AVCHFORZ.DEF" + "templates" : { + "castle" : { "animation" : "avchforx.def" }, + "village" : { "animation" : "AVCHFOR0.DEF" }, + "capitol" : { "animation" : "AVCHFORZ.DEF" } + } }, "structures" : { diff --git a/config/factions/dungeon.json b/config/factions/dungeon.json index 94340f036..7c15be082 100644 --- a/config/factions/dungeon.json +++ b/config/factions/dungeon.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCdunx0.def", - "village" : "AVCDUNG0.DEF", - "capitol" : "AVCDUNZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCdunx0.def" }, + "village" : { "animation" : "AVCDUNG0.DEF" }, + "capitol" : { "animation" : "AVCDUNZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/fortress.json b/config/factions/fortress.json index 2c2e84842..7dc1c924b 100644 --- a/config/factions/fortress.json +++ b/config/factions/fortress.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCftrx0.def", - "village" : "AVCFTRT0.DEF", - "capitol" : "AVCFORZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCftrx0.def" }, + "village" : { "animation" : "AVCFTRT0.DEF" }, + "capitol" : { "animation" : "AVCFORZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/inferno.json b/config/factions/inferno.json index 173d01bbb..15797e446 100644 --- a/config/factions/inferno.json +++ b/config/factions/inferno.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCinfx0.def", - "village" : "AVCINFT0.DEF", - "capitol" : "AVCINFZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCinfx0.def" }, + "village" : { "animation" : "AVCINFT0.DEF" }, + "capitol" : { "animation" : "AVCINFZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/necropolis.json b/config/factions/necropolis.json index 887606b6c..a12f4cd88 100644 --- a/config/factions/necropolis.json +++ b/config/factions/necropolis.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCnecx0.def", - "village" : "AVCNECR0.DEF", - "capitol" : "AVCNECZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCnecx0.def" }, + "village" : { "animation" : "AVCNECR0.DEF" }, + "capitol" : { "animation" : "AVCNECZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/rampart.json b/config/factions/rampart.json index c1cde4afb..af09ed901 100644 --- a/config/factions/rampart.json +++ b/config/factions/rampart.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCramx0.def", - "village" : "AVCRAMP0.DEF", - "capitol" : "AVCRAMZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCramx0.def" }, + "village" : { "animation" : "AVCRAMP0.DEF" }, + "capitol" : { "animation" : "AVCRAMZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/stronghold.json b/config/factions/stronghold.json index f45037261..f88e38472 100644 --- a/config/factions/stronghold.json +++ b/config/factions/stronghold.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCstrx0.def", - "village" : "AVCSTRO0.DEF", - "capitol" : "AVCSTRZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCstrx0.def" }, + "village" : { "animation" : "AVCSTRO0.DEF" }, + "capitol" : { "animation" : "AVCSTRZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/tower.json b/config/factions/tower.json index 99983052d..5a3f61cda 100644 --- a/config/factions/tower.json +++ b/config/factions/tower.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCtowx0.def", - "village" : "AVCTOWR0.DEF", - "capitol" : "AVCTOWZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCtowx0.def" }, + "village" : { "animation" : "AVCTOWR0.DEF" }, + "capitol" : { "animation" : "AVCTOWZ0.DEF" } + } }, "structures" : { diff --git a/config/gameConfig.json b/config/gameConfig.json index 374bbf9a8..801287573 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -45,6 +45,15 @@ "config/heroes/special.json" ], + "objects" : + [ + "config/objects/generic.json", + "config/objects/moddables.json", + "config/objects/creatureBanks.json", + "config/objects/dwellings.json", + "config/objects/rewardable.json" + ], + "artifacts" : [ "config/artifacts.json" diff --git a/config/heroClasses.json b/config/heroClasses.json index 0a1449113..1486e73a9 100644 --- a/config/heroClasses.json +++ b/config/heroClasses.json @@ -6,11 +6,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "zealot", - "animation": - { - "battle" : { "male" : "CH00.DEF", "female" : "CH01.DEF" }, - "map": { "male" : "AH00_.def", "female" : "AH00_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH00_.def" } } }, + "animation": { "battle" : { "male" : "CH00.DEF", "female" : "CH01.DEF" } } }, "cleric" : { @@ -19,11 +16,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "zealot", - "animation": - { - "battle" : { "male" : "CH00.DEF", "female" : "CH01.DEF" }, - "map": { "male" : "AH01_.def", "female" : "AH01_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH01_.def" } } }, + "animation": { "battle" : { "male" : "CH00.DEF", "female" : "CH01.DEF" } } }, "ranger" : { @@ -32,11 +26,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "grandElf", - "animation": - { - "battle" : { "male" : "CH02.DEF", "female" : "CH03.DEF" }, - "map": { "male" : "AH02_.def", "female" : "AH02_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH02_.def" } } }, + "animation": { "battle" : { "male" : "CH02.DEF", "female" : "CH03.DEF" } } }, "druid" : { @@ -45,11 +36,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "grandElf", - "animation": - { - "battle" : { "male" : "CH02.DEF", "female" : "CH03.DEF" }, - "map": { "male" : "AH03_.def", "female" : "AH03_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH03_.def" } } }, + "animation": { "battle" : { "male" : "CH02.DEF", "female" : "CH03.DEF" } } }, "alchemist" : { @@ -58,11 +46,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "archMage", - "animation": - { - "battle" : { "male" : "CH05.DEF", "female" : "CH04.DEF" }, - "map": { "male" : "AH04_.def", "female" : "AH04_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH04_.def" } } }, + "animation": { "battle" : { "male" : "CH05.DEF", "female" : "CH04.DEF" } } }, "wizard" : { @@ -71,11 +56,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "archMage", - "animation": - { - "battle" : { "male" : "CH05.DEF", "female" : "CH04.DEF" }, - "map": { "male" : "AH05_.def", "female" : "AH05_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH05_.def" } } }, + "animation": { "battle" : { "male" : "CH05.DEF", "female" : "CH04.DEF" } } }, "demoniac" : { @@ -84,11 +66,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "magog", - "animation": - { - "battle" : { "male" : "CH06.DEF", "female" : "CH07.DEF" }, - "map": { "male" : "AH06_.def", "female" : "AH06_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH06_.def" } } }, + "animation": { "battle" : { "male" : "CH06.DEF", "female" : "CH07.DEF" } } }, "heretic" : { @@ -97,11 +76,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "magog", - "animation": - { - "battle" : { "male" : "CH06.DEF", "female" : "CH07.DEF" }, - "map": { "male" : "AH07_.def", "female" : "AH07_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH07_.def" } } }, + "animation": { "battle" : { "male" : "CH06.DEF", "female" : "CH07.DEF" } } }, "deathknight" : { @@ -110,11 +86,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "powerLich", - "animation": - { - "battle" : { "male" : "CH08.DEF", "female" : "CH09.DEF" }, - "map": { "male" : "AH08_.def", "female" : "AH08_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH08_.def" } } }, + "animation": { "battle" : { "male" : "CH08.DEF", "female" : "CH09.DEF" } } }, "necromancer" : { @@ -123,11 +96,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "powerLich", - "animation": - { - "battle" : { "male" : "CH08.DEF", "female" : "CH09.DEF" }, - "map": { "male" : "AH09_.def", "female" : "AH09_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH09_.def" } } }, + "animation": { "battle" : { "male" : "CH08.DEF", "female" : "CH09.DEF" } } }, "warlock" : { @@ -136,11 +106,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "medusaQueen", - "animation": - { - "battle" : { "male" : "CH010.DEF", "female" : "CH11.DEF" }, - "map": { "male" : "AH10_.def", "female" : "AH10_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH10_.def" } } }, + "animation": { "battle" : { "male" : "CH010.DEF", "female" : "CH11.DEF" } } }, "overlord" : { @@ -149,11 +116,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "medusaQueen", - "animation": - { - "battle" : { "male" : "CH010.DEF", "female" : "CH11.DEF" }, - "map": { "male" : "AH11_.def", "female" : "AH11_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH11_.def" } } }, + "animation": { "battle" : { "male" : "CH010.DEF", "female" : "CH11.DEF" } } }, "barbarian" : { @@ -162,11 +126,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "orcChieftain", - "animation": - { - "battle" : { "male" : "CH013.DEF", "female" : "CH012.DEF" }, - "map": { "male" : "AH12_.def", "female" : "AH12_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH12_.def" } } }, + "animation": { "battle" : { "male" : "CH013.DEF", "female" : "CH012.DEF" } } }, "battlemage" : { @@ -175,11 +136,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "orcChieftain", - "animation": - { - "battle" : { "male" : "CH013.DEF", "female" : "CH012.DEF" }, - "map": { "male" : "AH13_.def", "female" : "AH13_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH13_.def" } } }, + "animation": { "battle" : { "male" : "CH013.DEF", "female" : "CH012.DEF" } } }, "beastmaster" : { @@ -188,11 +146,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "lizardWarrior", - "animation": - { - "battle" : { "male" : "CH014.DEF", "female" : "CH015.DEF" }, - "map": { "male" : "AH14_.def", "female" : "AH14_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH14_.def" } } }, + "animation": { "battle" : { "male" : "CH014.DEF", "female" : "CH015.DEF" } } }, "witch" : { @@ -201,11 +156,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "lizardWarrior", - "animation": - { - "battle" : { "male" : "CH014.DEF", "female" : "CH015.DEF" }, - "map": { "male" : "AH15_.def", "female" : "AH15_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH15_.def" } } }, + "animation": { "battle" : { "male" : "CH014.DEF", "female" : "CH015.DEF" } } }, "planeswalker" : { @@ -214,11 +166,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "iceElemental", - "animation": - { - "battle" : { "male" : "CH16.DEF", "female" : "CH16.DEF" }, - "map": { "male" : "AH16_.def", "female" : "AH16_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH16_.def" } } }, + "animation": { "battle" : { "male" : "CH16.DEF", "female" : "CH16.DEF" } } }, "elementalist" : { @@ -227,10 +176,7 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "iceElemental", - "animation": - { - "battle" : { "male" : "CH17.DEF", "female" : "CH17.DEF" }, - "map": { "male" : "AH17_.def", "female" : "AH17_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH17_.def" } } }, + "animation": { "battle" : { "male" : "CH17.DEF", "female" : "CH17.DEF" } } } } diff --git a/config/objects/creatureBanks.json b/config/objects/creatureBanks.json new file mode 100644 index 000000000..6cba397be --- /dev/null +++ b/config/objects/creatureBanks.json @@ -0,0 +1,973 @@ +{ + "creatureBank" : { + "index" :16, + "handler": "bank", + "types" : { + "cyclopsStockpile" : + { + "index" : 0, + "name" : "Cyclops Stockpile", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 4, "type": "cyclop" }, + { "amount": 4, "type": "cyclop" }, + { "amount": 4, "type": "cyclop", "upgradeChance": 50 }, + { "amount": 4, "type": "cyclop" }, + { "amount": 4, "type": "cyclop" } + ], + + "combat_value": 506, + "reward" : { + "value": 10000, + "resources": + { + "wood" : 4, + "mercury" : 4, + "ore" : 4, + "sulfur" : 4, + "crystal" : 4, + "gems" : 4, + "gold" : 0 + } + } + }, + + { + "chance": 30, + "guards": [ + { "amount": 6, "type": "cyclop" }, + { "amount": 6, "type": "cyclop" }, + { "amount": 6, "type": "cyclop", "upgradeChance": 50 }, + { "amount": 6, "type": "cyclop" }, + { "amount": 6, "type": "cyclop" } + ], + "combat_value": 760, + "reward" : { + "value": 15000, + "resources": + { + "wood" : 6, + "mercury" : 6, + "ore" : 6, + "sulfur" : 6, + "crystal" : 6, + "gems" : 6 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 8, "type": "cyclop" }, + { "amount": 8, "type": "cyclop" }, + { "amount": 8, "type": "cyclop", "upgradeChance": 50 }, + { "amount": 8, "type": "cyclop" }, + { "amount": 8, "type": "cyclop" } + ], + "combat_value": 1013, + "reward" : { + "value": 20000, + "resources": + { + "wood" : 8, + "mercury" : 8, + "ore" : 8, + "sulfur" : 8, + "crystal" : 8, + "gems" : 8 + } + } + }, + { + "chance": 10, + "guards": [ + { "amount": 10, "type": "cyclop" }, + { "amount": 10, "type": "cyclop" }, + { "amount": 10, "type": "cyclop", "upgradeChance": 50 }, + { "amount": 10, "type": "cyclop" }, + { "amount": 10, "type": "cyclop" } + ], + "combat_value": 1266, + "reward" : { + "value": 25000, + "resources": + { + "wood" : 10, + "mercury" : 10, + "ore" : 10, + "sulfur" : 10, + "crystal" : 10, + "gems" : 10 + } + } + } + ] + }, + "dwarvenTreasury" : { + "index" : 1, + "resetDuraition" : 28, + "name" : "Dwarven Treasury", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 10, "type": "dwarf" }, + { "amount": 10, "type": "dwarf" }, + { "amount": 10, "type": "dwarf", "upgradeChance": 50 }, + { "amount": 10, "type": "dwarf" }, + { "amount": 10, "type": "dwarf" } + ], + "combat_value": 194, + "reward" : { + "value": 3500, + "resources": + { + "crystal" : 2, + "gold" : 2500 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 15, "type": "dwarf" }, + { "amount": 15, "type": "dwarf" }, + { "amount": 15, "type": "dwarf", "upgradeChance": 50 }, + { "amount": 15, "type": "dwarf" }, + { "amount": 15, "type": "dwarf" } + ], + "combat_value": 291, + "reward" : { + "value": 5500, + "resources": + { + "crystal" : 3, + "gold" : 4000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 20, "type": "dwarf" }, + { "amount": 20, "type": "dwarf" }, + { "amount": 20, "type": "dwarf", "upgradeChance": 50 }, + { "amount": 20, "type": "dwarf" }, + { "amount": 20, "type": "dwarf" } + ], + "combat_value": 388, + "reward" : { + "value": 7500, + "resources": + { + "crystal" : 5, + "gold" : 5000 + } + } + }, + { + "chance": 10, + "guards": [ + { "amount": 30, "type": "dwarf" }, + { "amount": 30, "type": "dwarf" }, + { "amount": 30, "type": "dwarf", "upgradeChance": 50 }, + { "amount": 30, "type": "dwarf" }, + { "amount": 30, "type": "dwarf" } + ], + "combat_value": 582, + "reward" : { + "value": 12500, + "resources": + { + "crystal" : 10, + "gold" : 7500 + } + } + } + ] + }, + "griffinConservatory" : { + "index" : 2, + "resetDuraition" : 28, + "name" : "Griffin Conservatory", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 10, "type": "griffin" }, + { "amount": 10, "type": "griffin" }, + { "amount": 10, "type": "griffin", "upgradeChance": 50 }, + { "amount": 10, "type": "griffin" }, + { "amount": 10, "type": "griffin" } + ], + "combat_value": 351, + "reward" : { + "value": 3000, + "creatures": [ { "amount": 1, "type": "angel" } ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 20, "type": "griffin" }, + { "amount": 20, "type": "griffin" }, + { "amount": 20, "type": "griffin", "upgradeChance": 50 }, + { "amount": 20, "type": "griffin" }, + { "amount": 20, "type": "griffin" } + ], + "combat_value": 702, + "reward" : { + "value": 6000, + "creatures": [ { "amount": 2, "type": "angel" } ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 30, "type": "griffin" }, + { "amount": 30, "type": "griffin" }, + { "amount": 30, "type": "griffin", "upgradeChance": 50 }, + { "amount": 30, "type": "griffin" }, + { "amount": 30, "type": "griffin" } + ], + "combat_value": 1053, + "reward" : { + "value": 9000, + "creatures": [ { "amount": 3, "type": "angel" } ] + } + }, + { + "chance": 10, + "guards": [ + { "amount": 40, "type": "griffin" }, + { "amount": 40, "type": "griffin" }, + { "amount": 40, "type": "griffin", "upgradeChance": 50 }, + { "amount": 40, "type": "griffin" }, + { "amount": 40, "type": "griffin" } + ], + "combat_value": 1404, + "reward" : { + "value": 12000, + "creatures": [ { "amount": 4, "type": "angel" } ] + } + } + ] + }, + "inpCache" : { + "index" : 3, + "resetDuraition" : 28, + "name" : "Imp Cache", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 20, "type": "imp" }, + { "amount": 20, "type": "imp" }, + { "amount": 20, "type": "imp", "upgradeChance": 50 }, + { "amount": 20, "type": "imp" }, + { "amount": 20, "type": "imp" } + ], + "combat_value": 100, + "reward" : { + "value": 2000, + "resources": + { + "wood" : 0, + "mercury" : 2, + "ore" : 0, + "sulfur" : 0, + "crystal" : 0, + "gems" : 0, + "gold" : 1000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 30, "type": "imp" }, + { "amount": 30, "type": "imp" }, + { "amount": 30, "type": "imp", "upgradeChance": 50 }, + { "amount": 30, "type": "imp" }, + { "amount": 30, "type": "imp" } + ], + "combat_value": 150, + "reward" : { + "value": 3000, + "resources": + { + "mercury" : 3, + "gold" : 1500 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 40, "type": "imp" }, + { "amount": 40, "type": "imp" }, + { "amount": 40, "type": "imp", "upgradeChance": 50 }, + { "amount": 40, "type": "imp" }, + { "amount": 40, "type": "imp" } + ], + "combat_value": 200, + "reward" : { + "value": 4000, + "resources": + { + "mercury" : 4, + "gold" : 2000 + } + } + }, + { + "chance": 10, + "guards": [ + { "amount": 60, "type": "imp" }, + { "amount": 60, "type": "imp" }, + { "amount": 60, "type": "imp", "upgradeChance": 50 }, + { "amount": 60, "type": "imp" }, + { "amount": 60, "type": "imp" } + ], + "combat_value": 300, + "reward" : { + "value": 6000, + "resources": + { + "mercury" : 6, + "gold" : 3000 + } + } + } + ] + }, + "medusaStore" : { + "index" : 4, + "resetDuraition" : 28, + "name" : "Medusa Stores", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 4, "type": "medusa" }, + { "amount": 4, "type": "medusa" }, + { "amount": 4, "type": "medusa", "upgradeChance": 50 }, + { "amount": 4, "type": "medusa" }, + { "amount": 4, "type": "medusa" } + ], + "combat_value": 207, + "reward" : { + "value": 4500, + "resources": + { + "sulfur" : 5, + "gold" : 2000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 6, "type": "medusa" }, + { "amount": 6, "type": "medusa" }, + { "amount": 6, "type": "medusa", "upgradeChance": 50 }, + { "amount": 6, "type": "medusa" }, + { "amount": 6, "type": "medusa" } + ], + "combat_value": 310, + "reward" : { + "value": 6000, + "resources": + { + "sulfur" : 6, + "gold" : 3000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 8, "type": "medusa" }, + { "amount": 8, "type": "medusa" }, + { "amount": 8, "type": "medusa", "upgradeChance": 50 }, + { "amount": 8, "type": "medusa" }, + { "amount": 8, "type": "medusa" } + ], + "combat_value": 414, + "reward" : { + "value": 8000, + "resources": + { + "sulfur" : 8, + "gold" : 4000 + } + } + }, + { + "chance": 10, + "guards": [ + { "amount": 10, "type": "medusa" }, + { "amount": 10, "type": "medusa" }, + { "amount": 10, "type": "medusa", "upgradeChance": 50 }, + { "amount": 10, "type": "medusa" }, + { "amount": 10, "type": "medusa" } + ], + "combat_value": 517, + "reward" : { + "value": 10000, + "resources": + { + "sulfur" : 10, + "gold" : 5000 + } + } + } + ] + }, + "nagaBank" : { + "index" : 5, + "resetDuraition" : 28, + "name" : "Naga Bank", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 2, "type": "naga" }, + { "amount": 2, "type": "naga" }, + { "amount": 2, "type": "naga", "upgradeChance": 50 }, + { "amount": 2, "type": "naga" }, + { "amount": 2, "type": "naga" } + ], + "combat_value": 403, + "reward" : { + "value": 8000, + "resources": + { + "gems" : 8, + "gold" : 4000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 3, "type": "naga" }, + { "amount": 3, "type": "naga" }, + { "amount": 3, "type": "naga", "upgradeChance": 50 }, + { "amount": 3, "type": "naga" }, + { "amount": 3, "type": "naga" } + ], + "combat_value": 605, + "reward" : { + "value": 12000, + "resources": + { + "gems" : 12, + "gold" : 6000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 4, "type": "naga" }, + { "amount": 4, "type": "naga" }, + { "amount": 4, "type": "naga", "upgradeChance": 50 }, + { "amount": 4, "type": "naga" }, + { "amount": 4, "type": "naga" } + ], + "combat_value": 806, + "reward" : { + "value": 16000, + "resources": + { + "gems" : 16, + "gold" : 8000 + } + } + }, + { + "chance": 10, + "guards": [ + { "amount": 6, "type": "naga" }, + { "amount": 6, "type": "naga" }, + { "amount": 6, "type": "naga", "upgradeChance": 50 }, + { "amount": 6, "type": "naga" }, + { "amount": 6, "type": "naga" } + ], + "combat_value": 1210, + "reward" : { + "value": 24000, + "resources": + { + "gems" : 24, + "gold" : 12000 + } + } + } + ] + }, + "dragonflyHive" : { + "index" : 6, + "resetDuraition" : 28, + "name" : "Dragon Fly Hive", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 6, "type": "dragonFly" }, + { "amount": 6, "type": "dragonFly" }, + { "amount": 6, "type": "dragonFly" }, + { "amount": 6, "type": "dragonFly" }, + { "amount": 6, "type": "dragonFly" } + ], + "combat_value": 154, + "reward" : { + "value": 3200, + "creatures": [ { "amount": 4, "type": "vyvern" } ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 9, "type": "dragonFly" }, + { "amount": 9, "type": "dragonFly" }, + { "amount": 9, "type": "dragonFly" }, + { "amount": 9, "type": "dragonFly" }, + { "amount": 9, "type": "dragonFly" } + ], + "combat_value": 230, + "reward" : { + "value": 4800, + "creatures": [ { "amount": 6, "type": "vyvern" } ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 12, "type": "dragonFly" }, + { "amount": 12, "type": "dragonFly" }, + { "amount": 12, "type": "dragonFly" }, + { "amount": 12, "type": "dragonFly" }, + { "amount": 12, "type": "dragonFly" } + ], + "combat_value": 307, + "reward" : { + "value": 6400, + "creatures": [ { "amount": 8, "type": "vyvern" } ] + } + }, + { + "chance": 10, + "guards": [ + { "amount": 18, "type": "dragonFly" }, + { "amount": 18, "type": "dragonFly" }, + { "amount": 18, "type": "dragonFly" }, + { "amount": 18, "type": "dragonFly" }, + { "amount": 18, "type": "dragonFly" } + ], + "combat_value": 461, + "reward" : { + "value": 9600, + "creatures": [ { "amount": 12, "type": "vyvern" } ] + } + } + ] + } + } + }, + "shipwreck" : { + "index" :85, + "handler": "bank", + "types" : { + "shipwreck" : { + "index" : 0, + "resetDuraition" : 28, + "name" : "Shipwreck", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 2, "type": "wight" }, + { "amount": 2, "type": "wight" }, + { "amount": 2, "type": "wight" }, + { "amount": 2, "type": "wight" }, + { "amount": 2, "type": "wight" } + ], + "combat_value": 31, + "reward" : { + "value": 2000, + "resources": + { + "gold" : 2000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 3, "type": "wight" }, + { "amount": 3, "type": "wight" }, + { "amount": 3, "type": "wight" }, + { "amount": 3, "type": "wight" }, + { "amount": 3, "type": "wight" } + ], + "combat_value": 46, + "reward" : { + "value": 3000, + "resources": + { + "gold" : 3000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 5, "type": "wight" }, + { "amount": 5, "type": "wight" }, + { "amount": 5, "type": "wight" }, + { "amount": 5, "type": "wight" }, + { "amount": 5, "type": "wight" } + ], + "combat_value": 77, + "reward" : { + "value": 5000, + "resources": + { + "gold" : 4000 + }, + "artifacts": [ { "class" : "TREASURE" } ] + } + }, + { + "chance": 10, + "guards": [ + { "amount": 10, "type": "wight" }, + { "amount": 10, "type": "wight" }, + { "amount": 10, "type": "wight" }, + { "amount": 10, "type": "wight" }, + { "amount": 10, "type": "wight" } + ], + "combat_value": 154, + "reward" : { + "value": 7000, + "resources": + { + "gold" : 5000 + }, + "artifacts": [ { "class" : "MINOR" } ] + } + } + ] + } + } + }, + "derelictShip" : { + "index" :24, + "handler": "bank", + "types" : { + "derelictShip" : { + "index" : 0, + "resetDuraition" : 28, + "name" : "Derelict Ship", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 4, "type": "waterElemental" }, + { "amount": 4, "type": "waterElemental" }, + { "amount": 4, "type": "waterElemental" }, + { "amount": 4, "type": "waterElemental" }, + { "amount": 4, "type": "waterElemental" } + ], + "combat_value": 138, + "reward" : { + "value": 3000, + "resources": + { + "gold" : 3000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 6, "type": "waterElemental" }, + { "amount": 6, "type": "waterElemental" }, + { "amount": 6, "type": "waterElemental" }, + { "amount": 6, "type": "waterElemental" }, + { "amount": 6, "type": "waterElemental" } + ], + "combat_value": 207, + "reward" : { + "value": 4000, + "resources": + { + "gold" : 3000 + }, + "artifacts": [ { "class" : "TREASURE" } ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 8, "type": "waterElemental" }, + { "amount": 8, "type": "waterElemental" }, + { "amount": 8, "type": "waterElemental" }, + { "amount": 8, "type": "waterElemental" }, + { "amount": 8, "type": "waterElemental" } + ], + "combat_value": 276, + "reward" : { + "value": 5000, + "resources": + { + "gold" : 4000 + }, + "artifacts": [ { "class" : "TREASURE" } ] + } + }, + { + "chance": 10, + "guards": [ + { "amount": 12, "type": "waterElemental" }, + { "amount": 12, "type": "waterElemental" }, + { "amount": 12, "type": "waterElemental" }, + { "amount": 12, "type": "waterElemental" }, + { "amount": 12, "type": "waterElemental" } + ], + "combat_value": 414, + "reward" : { + "value": 8000, + "resources": + { + "gold" : 6000 + }, + "artifacts": [ { "class" : "MINOR" } ] + } + } + ] + } + } + }, + "crypt" : { + "index" :84, + "handler": "bank", + "types" : { + "crypt" : { + "index" : 0, + "resetDuraition" : 28, + "name" : "Crypt", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 10, "type": "skeleton" }, + { "amount": 10, "type": "walkingDead" }, + { "amount": 10, "type": "walkingDead" }, + { "amount": 10, "type": "skeleton" }, + { "amount": 10, "type": "skeleton" } + ], + "combat_value": 75, + "reward" : { + "value": 1500, + "resources": + { + "gold" : 1500 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 13, "type": "skeleton" }, + { "amount": 10, "type": "walkingDead" }, + { "amount": 5, "type": "wight" }, + { "amount": 10, "type": "walkingDead" }, + { "amount": 12, "type": "skeleton" } + ], + "combat_value": 94, + "reward" : { + "value": 2000, + "resources": + { + "gold" : 2000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 20, "type": "skeleton" }, + { "amount": 20, "type": "walkingDead" }, + { "amount": 10, "type": "wight" }, + { "amount": 5, "type": "vampire" } + ], + "combat_value": 169, + "reward" : { + "value": 3500, + "resources": + { + "gold" : 2500 + }, + "artifacts": [ { "class" : "TREASURE" } ] + } + }, + { + "chance": 10, + "guards": [ + { "amount": 20, "type": "skeleton" }, + { "amount": 20, "type": "walkingDead" }, + { "amount": 10, "type": "wight" }, + { "amount": 10, "type": "vampire" } + ], + "combat_value": 225, + "reward" : { + "value": 6000, + "resources": + { + "gold" : 5000 + }, + "artifacts": [ { "class" : "TREASURE" } ] + } + } + ] + } + } + }, + "dragonUtopia" : { + "index" :25, + "handler": "bank", + "types" : { + "dragonUtopia" : { + "index" : 0, + "resetDuraition" : 28, + "name" : "Dragon Utopia", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 8, "type": "greenDragon" }, + { "amount": 5, "type": "redDragon" }, + { "amount": 2, "type": "goldDragon" }, + { "amount": 1, "type": "blackDragon" } + ], + "combat_value": 769, + "reward" : { + "value": 38000, + "resources": + { + "gold" : 20000 + }, + "artifacts": [ + { "class" : "TREASURE" }, + { "class" : "MINOR" }, + { "class" : "MAJOR" }, + { "class" : "RELIC" } + ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 8, "type": "greenDragon" }, + { "amount": 6, "type": "redDragon" }, + { "amount": 3, "type": "goldDragon" }, + { "amount": 2, "type": "blackDragon" } + ], + "combat_value": 209, + "reward" : { + "value": 57000, + "resources": + { + "gold" : 30000 + }, + "artifacts": [ + { "class" : "MINOR" }, + { "class" : "MAJOR" }, + { "class" : "RELIC" }, + { "class" : "RELIC" } + ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 8, "type": "greenDragon" }, + { "amount": 6, "type": "redDragon" }, + { "amount": 4, "type": "goldDragon" }, + { "amount": 3, "type": "blackDragon" } + ], + "combat_value": 556, + "reward" : { + "value": 75000, + "resources": + { + "gold" : 40000 + }, + "artifacts": [ + { "class" : "MAJOR" }, + { "class" : "RELIC" }, + { "class" : "RELIC" }, + { "class" : "RELIC" } + ] + } + }, + { + "chance": 10, + "guards": [ + { "amount": 8, "type": "greenDragon" }, + { "amount": 7, "type": "redDragon" }, + { "amount": 6, "type": "goldDragon" }, + { "amount": 5, "type": "blackDragon" } + ], + "combat_value": 343, + "reward" : { + "value": 90000, + "resources": + { + "gold" : 50000 + }, + "artifacts": [ + { "class" : "RELIC" }, + { "class" : "RELIC" }, + { "class" : "RELIC" }, + { "class" : "RELIC" } + ] + } + } + ] + } + } + }, + "pyramid" : { + "index" :63, + "handler": "bank", + "types" : { + "pyramid" : { + "index" : 0, + "resetDuraition" : 28, + "name" : "Pyramid", + "levels": [ + { + "chance": 100, + "guards": [ + { "amount": 20, "type": "goldGolem" }, + { "amount": 10, "type": "diamondGolem" }, + { "amount": 20, "type": "goldGolem" }, + { "amount": 10, "type": "diamondGolem" } + ], + "combat_value": 786, + "reward" : { + "value": 15000, + "spells" : [ { "level" : 5 } ] + } + } + ] + } + } + } +} diff --git a/config/objects/dwellings.json b/config/objects/dwellings.json new file mode 100644 index 000000000..0f8150163 --- /dev/null +++ b/config/objects/dwellings.json @@ -0,0 +1,126 @@ +{ + "creatureGeneratorCommon" : { + "index" :17, + "handler": "dwelling", + "base" : { + "base" : { + "visitableFrom" : [ "---", "+++", "+++" ], + "mask" : [ "VVV", "VBB", "VAA" ] + } + }, + "types" : { + "basiliskPit" : { "index" : 0, "creatures" : [ [ "basilisk" ] ] }, + "behemothCrag" : { "index" : 1, "creatures" : [ [ "behemoth" ] ], "guards" : true }, + "pillarOfEyes" : { "index" : 2, "creatures" : [ [ "beholder" ] ] }, + "hallOfDarkness" : { "index" : 3, "creatures" : [ [ "blackKnight" ] ], "guards" : true }, + "dragonVault" : { "index" : 4, "creatures" : [ [ "boneDragon" ] ], "guards" : true }, + "trainingGrounds" : { "index" : 5, "creatures" : [ [ "cavalier" ] ], "guards" : true }, + "centaurStables" : { "index" : 6, "creatures" : [ [ "centaur" ] ] }, + "airConflux" : { "index" : 7, "creatures" : [ [ "airElemental" ] ] }, + "portalOfGlory" : { "index" : 8, "creatures" : [ [ "angel" ] ], "guards" : true }, + "cyclopsCave" : { "index" : 9, "creatures" : [ [ "cyclop" ] ], "guards" : true }, + "forsakenPalace" : { "index" : 10, "creatures" : [ [ "devil" ] ], "guards" : true }, + "serpentFlyHive" : { "index" : 11, "creatures" : [ [ "serpentFly" ] ] }, + "dwarfCottage" : { "index" : 12, "creatures" : [ [ "dwarf" ] ] }, + "earthConflux" : { "index" : 13, "creatures" : [ [ "earthElemental" ] ], "guards" : true }, + "fireLake" : { "index" : 14, "creatures" : [ [ "efreet" ] ], "guards" : true }, + "homestead" : { "index" : 15, "creatures" : [ [ "woodElf" ] ] }, + "fireConflux" : { "index" : 16, "creatures" : [ [ "fireElemental" ] ] }, + "parapet" : { "index" : 17, "creatures" : [ [ "stoneGargoyle" ] ] }, + "altarOfWishes" : { "index" : 18, "creatures" : [ [ "genie" ] ], "guards" : true }, + "wolfPen" : { "index" : 19, "creatures" : [ [ "goblinWolfRider" ] ] }, + "gnollHut" : { "index" : 20, "creatures" : [ [ "gnoll" ] ] }, + "goblinBarracks" : { "index" : 21, "creatures" : [ [ "goblin" ] ] }, + "hallOfSins" : { "index" : 22, "creatures" : [ [ "gog" ] ] }, + "gorgonLair" : { "index" : 23, "creatures" : [ [ "gorgon" ] ], "guards" : true }, + "dragonCliffs" : { "index" : 24, "creatures" : [ [ "greenDragon" ] ], "guards" : true }, + "griffinTower" : { "index" : 25, "creatures" : [ [ "griffin" ] ] }, + "harpyLoft" : { "index" : 26, "creatures" : [ [ "harpy" ] ] }, + "kennels" : { "index" : 27, "creatures" : [ [ "hellHound" ] ] }, + "hydraPond" : { "index" : 28, "creatures" : [ [ "hydra" ] ], "guards" : true }, + "impCrucible" : { "index" : 29, "creatures" : [ [ "imp" ] ] }, + "lizardDen" : { "index" : 30, "creatures" : [ [ "lizardman" ] ] }, + "mageTower" : { "index" : 31, "creatures" : [ [ "mage" ] ] }, + "manticoreLair" : { "index" : 32, "creatures" : [ [ "manticore" ] ], "guards" : true }, + "medusaChapel" : { "index" : 33, "creatures" : [ [ "medusa" ] ] }, + "labyrinth" : { "index" : 34, "creatures" : [ [ "minotaur" ] ], "guards" : true }, + "monastery" : { "index" : 35, "creatures" : [ [ "monk" ] ], "guards" : true }, + "goldenPavilion" : { "index" : 36, "creatures" : [ [ "naga" ] ], "guards" : true }, + "demonGate" : { "index" : 37, "creatures" : [ [ "demon" ] ] }, + "ogreFort" : { "index" : 38, "creatures" : [ [ "ogre" ] ] }, + "orcTower" : { "index" : 39, "creatures" : [ [ "orc" ] ] }, + "hellHole" : { "index" : 40, "creatures" : [ [ "pitFiend" ] ], "guards" : true }, + "dragonCave" : { "index" : 41, "creatures" : [ [ "redDragon" ] ], "guards" : true }, + "cliffNest" : { "index" : 42, "creatures" : [ [ "roc" ] ], "guards" : true }, + "workshop" : { "index" : 43, "creatures" : [ [ "gremlin" ] ] }, + "cloudTemple" : { "index" : 44, "creatures" : [ [ "giant" ] ], "guards" : true }, + "dendroidArches" : { "index" : 45, "creatures" : [ [ "dendroidGuard" ] ], "guards" : true }, + "warren" : { "index" : 46, "creatures" : [ [ "troglodyte" ] ] }, + "waterConflux" : { "index" : 47, "creatures" : [ [ "waterElemental" ] ] }, + "tombOfSouls" : { "index" : 48, "creatures" : [ [ "wight" ] ] }, + "wyvernNest" : { "index" : 49, "creatures" : [ [ "wyvern" ] ], "guards" : true }, + "enchantedSpring" : { "index" : 50, "creatures" : [ [ "pegasus" ] ] }, + "unicornGladeBig" : { "index" : 51, "creatures" : [ [ "unicorn" ] ], "guards" : true }, + "mausoleum" : { "index" : 52, "creatures" : [ [ "lich" ] ], "guards" : true }, + "estate" : { "index" : 53, "creatures" : [ [ "vampire" ] ] }, + "cursedTemple" : { "index" : 54, "creatures" : [ [ "skeleton" ] ] }, + "graveyard" : { "index" : 55, "creatures" : [ [ "walkingDead" ] ] }, + "guardhouse" : { "index" : 56, "creatures" : [ [ "pikeman" ] ] }, + "archersTower" : { "index" : 57, "creatures" : [ [ "archer" ] ] }, + "barracks" : { "index" : 58, "creatures" : [ [ "swordsman" ] ] }, + "magicLantern" : { "index" : 59, "creatures" : [ [ "pixie" ] ] }, + "altarOfThought" : { "index" : 60, "creatures" : [ [ "psychicElemental" ] ], "guards" : true }, + "pyre" : { "index" : 61, "creatures" : [ [ "firebird" ] ], "guards" : true }, + "frozenCliffs" : { "index" : 62, "creatures" : [ [ "azureDragon" ] ], "guards" : true }, + "crystalCavern" : { "index" : 63, "creatures" : [ [ "crystalDragon" ] ], "guards" : true }, + "magicForest" : { "index" : 64, "creatures" : [ [ "fairieDragon" ] ], "guards" : true }, + "sulfurousLair" : { "index" : 65, "creatures" : [ [ "rustDragon" ] ], "guards" : true }, + "enchantersHollow" : { "index" : 66, "creatures" : [ [ "enchanter" ] ], "guards" : true }, + "treetopTower" : { "index" : 67, "creatures" : [ [ "sharpshooter" ] ], "guards" : true }, + "unicornGlade" : { "index" : 68, "creatures" : [ [ "unicorn" ] ], "guards" : true }, + "altarOfAir" : { "index" : 69, "creatures" : [ [ "airElemental" ] ] }, + "altarOfEarth" : { "index" : 70, "creatures" : [ [ "earthElemental" ] ], "guards" : true }, + "altarOfFire" : { "index" : 71, "creatures" : [ [ "fireElemental" ] ] }, + "altarOfWater" : { "index" : 72, "creatures" : [ [ "waterElemental" ] ] }, + "thatchedHut" : { "index" : 73, "creatures" : [ [ "halfling" ] ] }, + "hovel" : { "index" : 74, "creatures" : [ [ "peasant" ] ] }, + "boarGlen" : { "index" : 75, "creatures" : [ [ "boar" ] ] }, + "tombOfCurses" : { "index" : 76, "creatures" : [ [ "mummy" ] ] }, + "nomadTent" : { "index" : 77, "creatures" : [ [ "nomad" ] ] }, + "rogueCavern" : { "index" : 78, "creatures" : [ [ "rogue" ] ] }, + "trollBridge" : { "index" : 79, "creatures" : [ [ "troll" ] ], "guards" : true } + } + }, + // subtype: unique special dwellings - golem factory and elemental conflux + "creatureGeneratorSpecial" : { + "index" :20, + "handler": "dwelling", + "types" : { + "elementalConflux" : { + "index" : 0, + "creatures" : [ // 4 separate "levels" to give them separate growth + [ "airElemental" ], + [ "waterElemental" ], + [ "fireElemental" ], + [ "earthElemental" ] + ], + "guards" : [ + { "amount" : 12, "type" : "earthElemental" } + ] + }, + "golemFactory" : { + "index" : 1, + "creatures" : [ // 4 separate "levels" to give them separate growth + [ "ironGolem" ], + [ "stoneGolem" ], + [ "goldGolem" ], + [ "diamondGolem" ] + ], + "guards" : [ + { "amount" : 9, "type" : "goldGolem" }, + { "amount" : 6, "type" : "diamondGolem" } + ] + } + } + }, +} diff --git a/config/objects/generic.json b/config/objects/generic.json new file mode 100644 index 000000000..bc1e1dcf8 --- /dev/null +++ b/config/objects/generic.json @@ -0,0 +1,133 @@ +{ + /// These are objects that can not be configured, either due to + /// their hardcoded status or because they don't have any configurable functionality + "prison" : { + "index" :62, + "handler": "prison", + "types" : { + "prison" : { "index" : 0 } + } + }, + + "altarOfSacrifice" : { "index" :2, "handler": "market" }, + "tradingPost" : { "index" :221, "handler": "market" }, + "tradingPostDUPLICATE" : { "index" :99, "handler": "market" }, + "freelancersGuild" : { "index" :213, "handler": "market" }, + + "blackMarket" : { "index" :7, "handler": "blackMarket" }, + + "pandoraBox" : { "index" :6, "handler": "pandora" }, + "event" : { "index" :26, "handler": "event" }, + + "redwoodObservatory" : { "index" :58, "handler": "observatory" }, + "pillarOfFire" : { "index" :60, "handler": "observatory" }, + "coverOfDarkness" : { "index" :15, "handler": "observatory" }, + + "subterraneanGate" : { "index" :103, "handler": "teleport" }, + "whirlpool" : { "index" :111, "handler": "teleport" }, + + "refugeeCamp" : { "index" :78, "handler": "dwelling" }, + "warMachineFactory" : { "index" :106, "handler": "dwelling" }, + + "shrineOfMagicLevel1" : { "index" :88, "handler": "shrine" }, + "shrineOfMagicLevel2" : { "index" :89, "handler": "shrine" }, + "shrineOfMagicLevel3" : { "index" :90, "handler": "shrine" }, + + "eyeOfTheMagi" : { "index" :27, "handler": "magi" }, + "hutOfTheMagi" : { "index" :37, "handler": "magi" }, + + "lighthouse" : { "index" :42, "handler": "lighthouse" }, + "magicWell" : { "index" :49, "handler": "magicWell" }, + "obelisk" : { "index" :57, "handler": "obelisk" }, + "oceanBottle" : { "index" :59, "handler": "sign" }, + "scholar" : { "index" :81, "handler": "scholar" }, + "shipyard" : { "index" :87, "handler": "shipyard" }, + "sign" : { "index" :91, "handler": "sign" }, + "sirens" : { "index" :92, "handler": "siren" }, + "denOfThieves" : { "index" :97, "handler": "denOfThieves" }, + "university" : { "index" :104, "handler": "university" }, + "witchHut" : { "index" :113, "handler": "witch" }, + "questGuard" : { "index" :215, "handler": "questGuard" }, + + /// Random objects + "randomResource" : { "index" :76, "handler": "resource" }, + "randomTown" : { "index" :77, "handler": "town" }, + "randomHero" : { "index" :70, "handler": "hero" }, + + "randomDwelling" : { "index" :216, "handler": "dwelling" }, + + "randomArtifact" : { "index" :65, "handler": "artifact" }, + "randomArtifactTreasure" : { "index" :66, "handler": "artifact" }, + "randomArtifactMinor" : { "index" :67, "handler": "artifact" }, + "randomArtifactMajor" : { "index" :68, "handler": "artifact" }, + "randomArtifactRelic" : { "index" :69, "handler": "artifact" }, + + "randomMonster" : { "index" :71, "handler": "monster" }, + "randomMonsterLevel1" : { "index" :72, "handler": "monster" }, + "randomMonsterLevel2" : { "index" :73, "handler": "monster" }, + "randomMonsterLevel3" : { "index" :74, "handler": "monster" }, + "randomMonsterLevel4" : { "index" :75, "handler": "monster" }, + "randomMonsterLevel5" : { "index" :162, "handler": "monster" }, + "randomMonsterLevel6" : { "index" :163, "handler": "monster" }, + "randomMonsterLevel7" : { "index" :164, "handler": "monster" }, + + /// Classes without dedicated object + "hillFort" : { "index" :35, "handler": "generic" }, + "grail" : { "index" :36, "handler": "generic" }, + "tavern" : { "index" :95, "handler": "generic" }, + "sanctuary" : { "index" :80, "handler": "generic" }, + + /// Passive objects, terrain overlays + "cursedGround" : { "index" :21, "handler": "generic" }, + "magicPlains" : { "index" :46, "handler": "generic" }, + "swampFoliage" : { "index" :211, "handler": "generic" }, + "cloverField" : { "index" :222, "handler": "generic" }, + "cursedGroundDUPLICATE" : { "index" :223, "handler": "generic" }, + "evilFog" : { "index" :224, "handler": "generic" }, + "favorableWinds" : { "index" :225, "handler": "generic" }, + "fieryFields" : { "index" :226, "handler": "generic" }, + "holyGround" : { "index" :227, "handler": "generic" }, + "lucidPools" : { "index" :228, "handler": "generic" }, + "magicClouds" : { "index" :229, "handler": "generic" }, + "magicPlainsDUPLICATE" : { "index" :230, "handler": "generic" }, + "rocklands" : { "index" :231, "handler": "generic" }, + + /// Decorations + "cactus" : { "index" :116, "handler": "static" }, + "canyon" : { "index" :117, "handler": "static" }, + "crater" : { "index" :118, "handler": "static" }, + "deadVegetation" : { "index" :119, "handler": "static" }, + "flowers" : { "index" :120, "handler": "static" }, + "frozenLake" : { "index" :121, "handler": "static" }, + "hole" : { "index" :124, "handler": "static" }, + "kelp" : { "index" :125, "handler": "static" }, + "lake" : { "index" :126, "handler": "static" }, + "lavaFlow" : { "index" :127, "handler": "static" }, + "lavaLake" : { "index" :128, "handler": "static" }, + "mushrooms" : { "index" :129, "handler": "static" }, + "log" : { "index" :130, "handler": "static" }, + "mandrake" : { "index" :131, "handler": "static" }, + "moss" : { "index" :132, "handler": "static" }, + "mound" : { "index" :133, "handler": "static" }, + "mountain" : { "index" :134, "handler": "static" }, + "oakTrees" : { "index" :135, "handler": "static" }, + "outcropping" : { "index" :136, "handler": "static" }, + "pineTrees" : { "index" :137, "handler": "static" }, + "riverDelta" : { "index" :143, "handler": "static" }, + "rock" : { "index" :147, "handler": "static" }, + "sandDune" : { "index" :148, "handler": "static" }, + "sandPit" : { "index" :149, "handler": "static" }, + "shrub" : { "index" :150, "handler": "static" }, + "skull" : { "index" :151, "handler": "static" }, + "stump" : { "index" :153, "handler": "static" }, + "trees" : { "index" :155, "handler": "static" }, + "volcano" : { "index" :158, "handler": "static" }, + "reef" : { "index" :161, "handler": "static" }, + "lakeDUPLICATE" : { "index" :177, "handler": "static" }, + "treesDUPLICATE" : { "index" :199, "handler": "static" }, + "desertHills" : { "index" :206, "handler": "static" }, + "dirtHills" : { "index" :207, "handler": "static" }, + "grassHills" : { "index" :208, "handler": "static" }, + "roughHills" : { "index" :209, "handler": "static" }, + "subterraneanRocks" : { "index" :210, "handler": "static" } +} diff --git a/config/objects/moddables.json b/config/objects/moddables.json new file mode 100644 index 000000000..b03c5f6a5 --- /dev/null +++ b/config/objects/moddables.json @@ -0,0 +1,208 @@ +{ + /// These are objects that have subtypes that change various aspects of their mechanics + /// Should be made configurable (either directly or via other parts of modding system ASAP) + /// Editing these objects either directly or via mod may have negative effect on game since they are handled by engine + + // subtype: artifact ID + "artifact" : { + "index" :5, + "handler": "artifact", + "base" : { + "base" : { + "visitableFrom" : [ "+++", "+-+", "+++" ], + "mask" : [ "VV", "VA"] + } + } + }, + + // subtype: hero CLASS (not hero). + "hero" : { + "index" :34, + "handler": "hero", + "base" : { + "base" : { + "visitableFrom" : [ "+++", "+-+", "+++" ], + "mask" : [ "VV", "AV"] + } + } + }, + + // subtype: creatures + "monster" : { + "index" :54, + "handler": "monster", + "base" : { + "base" : { + "visitableFrom" : [ "+++", "+-+", "+++" ], + "mask" : [ "VV", "VA"] + } + } + }, + + // subtype: resource ID + "resource" : { + "index" :79, + "handler": "resource", + "base" : { + "base" : { + "visitableFrom" : [ "+++", "+-+", "+++" ], + "mask" : [ "VA" ] + } + }, + "types" : { + "wood" : { "index" : 0, "templates" : { "res" : { "animation" : "AVTwood0.def" } } }, + "mercury" : { "index" : 1, "templates" : { "res" : { "animation" : "AVTmerc0.def" } } }, + "ore" : { "index" : 2, "templates" : { "res" : { "animation" : "AVTore0.def" } } }, + "sulfur" : { "index" : 3, "templates" : { "res" : { "animation" : "AVTsulf0.def" } } }, + "crystal" : { "index" : 4, "templates" : { "res" : { "animation" : "AVTcrys0.def" } } }, + "gems" : { "index" : 5, "templates" : { "res" : { "animation" : "AVTgems0.def" } } }, + "gold" : { "index" : 6, "templates" : { "res" : { "animation" : "AVTgold0.def" } } }, + "mithril" : { "index" : 7 } // TODO: move to WoG? + } + }, + + // subtype: faction + "town" : { + "index" :98, + "handler": "town", + "base" : { + "filters" : { + // village image - fort not present + "village" : [ "noneOf", [ "fort" ] ], + // fort image - fort is here but not capitol + "fort" : [ "allOf", [ "fort" ], [ "noneOf", ["capitol" ] ] ], + // capitol image only when capitol is built + "capitol" : [ "capitol" ] + }, + + // "faction" : "stringID", // should be set by engine + "base" : { + "visitableFrom" : [ "---", "+++", "+++" ], + "mask" : [ + "VVVVVV", // a LOT of just visible rows due to towns like Tower + "VVVVVV", + "VVVVVV", + "VVBBBV", + "VBBBBB", + "VBBABB" + ] + } + } + }, + + // subtype: one of 3 possible boats + "boat" : { + "index" :8, + "handler": "boat", + "base" : { + "base" : { + "visitableFrom" : [ "+++", "+-+", "+++" ], + "mask" : [ "VVV", "VAV" ] + } + }, + "types" : { + "evil" : { "index" : 0 }, + "good" : { "index" : 1 }, + "neutral" : { "index" : 2 }, + } + }, + + // subtype: color of guard + "borderGuard" : { + "index" :9, + "handler": "borderGuard", + "types" : { + "lblue" : { "index" : 0 }, + "green" : { "index" : 1 }, + "red" : { "index" : 2 }, + "dblue" : { "index" : 3 }, + "brown" : { "index" : 4 }, + "purple" : { "index" : 5 }, + "white" : { "index" : 6 }, + "black" : { "index" : 7 } + } + }, + "borderGate" : { + "index" :212, + "handler": "borderGate", + "types" : { + "lblue" : { "index" : 0 }, + "green" : { "index" : 1 }, + "red" : { "index" : 2 }, + "dblue" : { "index" : 3 }, + "brown" : { "index" : 4 }, + "purple" : { "index" : 5 }, + "white" : { "index" : 6 }, + "black" : { "index" : 7 } + } + }, + "keymasterTent" : { + "index" :10, + "handler": "keymaster", + "types" : { + "lblue" : { "index" : 0 }, + "green" : { "index" : 1 }, + "red" : { "index" : 2 }, + "dblue" : { "index" : 3 }, + "brown" : { "index" : 4 }, + "purple" : { "index" : 5 }, + "white" : { "index" : 6 }, + "black" : { "index" : 7 } + } + }, + + // subtype: different revealed areas + "cartographer" : { + "index" :13, + "handler": "cartographer", + "types" : { + "water" : { "index" : 0 }, + "land" : { "index" : 1 }, + "subterra" : { "index" : 2 } + } + }, + + // subtype: resource ID + "mine" : { + "index" :53, + "handler": "mine", + "types" : { + "sawmill" : { "index" : 0 }, + "alchemistLab" : { "index" : 1 }, + "orePit" : { "index" : 2 }, + "sulfurDune" : { "index" : 3 }, + "crystalCavern" : { "index" : 4 }, + "gemPond" : { "index" : 5 }, + "goldMine" : { "index" : 6 }, + } + }, + "abandonedMine" : { + "index" :220, + "handler": "mine", + "types" : { + "mine" : { "index" : 7 } + } + }, + + // subtype: 0 = normal, 1 = anti-magic + "garrisonHorizontal" : { "index" :33, "handler": "garrison" }, + "garrisonVertical" : { "index" :219, "handler": "garrison" }, + + // Subtype: paired monoliths + "monolithOneWayEntrance" : { "index" :43, "handler": "teleport" }, + "monolithOneWayExit" : { "index" :44, "handler": "teleport" }, + "monolithTwoWay" : { "index" :45, "handler": "teleport" }, + + // subtype: different appearance. That's all? + "seerHut" : { "index" :83, "handler": "seerHut" }, + + // subtype: level + "randomDwellingLvl" : { "index" :217, "handler": "dwelling" }, + + // subtype: faction ID + "randomDwellingFaction" : { "index" :218, "handler": "dwelling" }, + + // don't have subtypes (at least now), but closely connected to this objects + "spellScroll" : { "index" :93, "handler": "artifact" }, + "heroPlaceholder" : { "index" :214, "handler": "heroPlaceholder" } +} diff --git a/config/objects/rewardable.json b/config/objects/rewardable.json new file mode 100644 index 000000000..7b6a06984 --- /dev/null +++ b/config/objects/rewardable.json @@ -0,0 +1,44 @@ +{ + /// These are objects that covered by concept of "configurable object" + /// Most or even all of their configuration located in this file + "magicSpring" : { "index" :48, "handler": "magicSpring" }, + + "mysticalGarden" : { "index" :55, "handler": "oncePerWeek" }, + "windmill" : { "index" :112, "handler": "oncePerWeek" }, + "waterWheel" : { "index" :109, "handler": "oncePerWeek" }, + + "leanTo" : { "index" :39, "handler": "onceVisitable" }, + "corpse" : { "index" :22, "handler": "onceVisitable" }, + "wagon" : { "index" :105, "handler": "onceVisitable" }, + "warriorTomb" : { "index" :108, "handler": "onceVisitable" }, + + "campfire" : { "index" :12, "handler": "pickable" }, + "flotsam" : { "index" :29, "handler": "pickable" }, + "seaChest" : { "index" :82, "handler": "pickable" }, + "shipwreckSurvivor" : { "index" :86, "handler": "pickable" }, + "treasureChest" : { "index" :101, "handler": "pickable" }, + + "arena" : { "index" :4, "handler": "oncePerHero" }, + "marlettoTower" : { "index" :23, "handler": "oncePerHero" }, + "gardenOfRevelation" : { "index" :32, "handler": "oncePerHero" }, + "libraryOfEnlightenment" : { "index" :41, "handler": "oncePerHero" }, + "mercenaryCamp" : { "index" :51, "handler": "oncePerHero" }, + "starAxis" : { "index" :61, "handler": "oncePerHero" }, + "learningStone" : { "index" :100, "handler": "oncePerHero" }, + "treeOfKnowledge" : { "index" :102, "handler": "oncePerHero" }, + "schoolOfMagic" : { "index" :47, "handler": "oncePerHero" }, + "schoolOfWar" : { "index" :107, "handler": "oncePerHero" }, + + "buoy" : { "index" :11, "handler": "bonusingObject" }, + "swanPond" : { "index" :14, "handler": "bonusingObject" }, + "faerieRing" : { "index" :28, "handler": "bonusingObject" }, + "fountainOfFortune" : { "index" :30, "handler": "bonusingObject" }, + "fountainOfYouth" : { "index" :31, "handler": "bonusingObject" }, + "idolOfFortune" : { "index" :38, "handler": "bonusingObject" }, + "mermaids" : { "index" :52, "handler": "bonusingObject" }, + "oasis" : { "index" :56, "handler": "bonusingObject" }, + "stables" : { "index" :94, "handler": "bonusingObject" }, + "temple" : { "index" :96, "handler": "bonusingObject" }, + "rallyFlag" : { "index" :64, "handler": "bonusingObject" }, + "wateringHole" : { "index" :110, "handler": "bonusingObject" }, +} diff --git a/config/rmg.json b/config/rmg.json index 0ac7bf064..9df21b1b8 100644 --- a/config/rmg.json +++ b/config/rmg.json @@ -9,26 +9,48 @@ "1" : { "type" : "playerStart", "size" : 1, "owner" : 1, - "playerTowns" : { "castles" : 1 }, "neutralTowns" : { "towns" : 1 }, "townsAreSameType" : true + "playerTowns" : { "castles" : 1 }, "neutralTowns" : { "towns" : 1 }, "townsAreSameType" : true, + "monsters" : "normal", + "mines" : {"wood" : 1, "ore" : 1, "gems" : 1, "crystal" : 1, "sulfur" : 1, "mercury" : 1}, + "treasure" : [ + {"min" : 2100, "max": 3000, "density" : 5}, + {"min" : 300, "max": 1500, "density" : 10} + ] }, "2" : { "type" : "playerStart", "size" : 1, "owner" : 2, - "playerTowns" : { "castles" : 1 }, "neutralTowns" : { "towns" : 1 }, "townsAreSameType" : true + "playerTowns" : { "castles" : 1 }, "neutralTowns" : { "towns" : 1 }, "townsAreSameType" : true, + "monsters" : "normal", + "minesLikeZone" : 1, + "treasureLikeZone" : 1 }, "3" : { "type" : "playerStart", "size" : 1, "owner" : 3, - "playerTowns" : { "castles" : 1 }, "neutralTowns" : { "towns" : 1 }, "townsAreSameType" : true + "playerTowns" : { "castles" : 1 }, "neutralTowns" : { "towns" : 1 }, "townsAreSameType" : true, + "monsters" : "normal", + "minesLikeZone" : 1, + "treasureLikeZone" : 1 }, "4" : { "type" : "playerStart", "size" : 1, "owner" : 4, - "playerTowns" : { "castles" : 1 }, "neutralTowns" : { "towns" : 1 }, "townsAreSameType" : true + "playerTowns" : { "castles" : 1 }, "neutralTowns" : { "towns" : 1 }, "townsAreSameType" : true, + "monsters" : "normal", + "minesLikeZone" : 1, + "treasureLikeZone" : 1 }, "5" : { - "type" : "treasure", "size" : 2, "terrainTypes" : [ "sand" ], "neutralTowns" : { "castles" : 1 } + "type" : "treasure", "size" : 2, "terrainTypes" : [ "sand" ], "matchTerrainToTown" : false, + "neutralTowns" : { "castles" : 1 }, + "monsters" : "strong", + "mines" : {"gold" : 2}, + "treasure" : [ + {"min" : 9000, "max": 10000, "density" : 3}, + {"min" : 6000, "max": 10000, "density" : 15} + ] } }, "connections" : @@ -48,34 +70,58 @@ "1" : { "type" : "playerStart", "size" : 1, "owner" : 1, - "playerTowns" : { "castles" : 1 } + "playerTowns" : { "castles" : 1 }, + "monsters" : "normal", + "mines" : {"wood" : 1, "ore" : 1}, + "treasure" : [ + {"min" : 400, "max": 1500, "density" : 16}, + {"min" : 1500, "max": 2500, "density" : 4} + ] }, "2" : { "type" : "playerStart", "size" : 1, "owner" : 2, - "playerTowns" : { "castles" : 1 } + "playerTowns" : { "castles" : 1 }, + "monsters" : "normal", + "minesLikeZone" : 1, + "treasureLikeZone" : 1 }, "3" : { - "type" : "treasure", "size" : 2, "neutralTowns" : { "towns" : 1 }, "townTypeLikeZone" : "1" + "type" : "treasure", "size" : 2, "neutralTowns" : { "towns" : 1 }, "townTypeLikeZone" : 1, + "monsters" : "weak", + "mines" : {"gems" : 1, "crystal" : 1, "sulfur" : 1, "mercury" : 1, "gold" : 1}, + "treasure" : [ + {"min" : 2000, "max": 4000, "density" : 15}, + {"min" : 4000, "max": 5000, "density" : 5} + ] }, "4" : { - "type" : "treasure", "size" : 2, "neutralTowns" : { "towns" : 1 }, "townTypeLikeZone" : "2" + "type" : "treasure", "size" : 2, "neutralTowns" : { "towns" : 1 }, "townTypeLikeZone" : 2, + "monsters" : "weak", + "minesLikeZone" : 3, + "treasureLikeZone" : 3 }, "5" : { - "type" : "treasure", "size" : 3, "neutralTowns" : { "castles" : 1 }, "terrainTypes" : [ "sand" ] + "type" : "treasure", "size" : 3, "neutralTowns" : { "castles" : 1 }, "terrainTypes" : [ "sand" ], + "monsters" : "strong", + "mines" : {"gold" : 2}, + "treasure" : [ + {"min" : 11000, "max": 12000, "density" : 5}, + {"min" : 6000, "max": 11000, "density" : 10} + ] } }, "connections" : [ - { "a" : "1", "b" : "3", "guard" : 1000 }, - { "a" : "1", "b" : "5", "guard" : 4000 }, - { "a" : "2", "b" : "4", "guard" : 1000 }, - { "a" : "2", "b" : "5", "guard" : 4000 }, - { "a" : "3", "b" : "5", "guard" : 2000 }, - { "a" : "4", "b" : "5", "guard" : 2000 } + { "a" : "1", "b" : "3", "guard" : 3000 }, + { "a" : "1", "b" : "5", "guard" : 9000 }, + { "a" : "2", "b" : "4", "guard" : 3000 }, + { "a" : "2", "b" : "5", "guard" : 9000 }, + { "a" : "3", "b" : "5", "guard" : 6000 }, + { "a" : "4", "b" : "5", "guard" : 6000 } ] }, "Golden Ring" : @@ -87,36 +133,63 @@ "1" : { "type" : "playerStart", "size" : 3, "owner" : 1, - "playerTowns" : { "castles" : 1 } + "playerTowns" : { "castles" : 1 }, + "monsters" : "normal", + "mines" : {"wood" : 1, "ore" : 1}, + "treasure" : [ + {"min" : 300, "max": 2000, "density": 15}, + {"min" : 2100, "max": 2500, "density": 5} + ] }, "2" : { "type" : "playerStart", "size" : 3, "owner" : 2, - "playerTowns" : { "castles" : 1 } + "playerTowns" : { "castles" : 1 }, + "monsters" : "normal", + "minesLikeZone" : 1, + "treasureLikeZone" : 1 }, "3" : { "type" : "playerStart", "size" : 3, "owner" : 3, - "playerTowns" : { "castles" : 1 } + "playerTowns" : { "castles" : 1 }, + "monsters" : "normal", + "minesLikeZone" : 1, + "treasureLikeZone" : 1 }, - "4" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : "1" }, - "5" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : "1" }, - "6" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : "2" }, - "7" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : "2" }, - "8" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : "3" }, - "9" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : "3" }, - "10" : { "type" : "treasure", "size" : 1, "neutralTowns" : { "towns" : 1 } }, - "11" : { "type" : "treasure", "size" : 1, "neutralTowns" : { "towns" : 1 } }, - "12" : { "type" : "treasure", "size" : 1, "neutralTowns" : { "towns" : 1 } } + "4" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : 1, + "monsters" : "normal", + "mines" : {"gems" : 1, "crystal" : 1}, + "treasure" : [ + {"min" : 3000, "max": 10000, "density" : 12}, + {"min" : 6000, "max": 10000, "density" : 6} + ]}, + "5" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : 1, + "monsters" : "normal", + "mines" : {"sulfur" : 1, "mercury" : 1}, + "treasureLikeZone" : 4}, + "6" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : 2, "monsters" : "normal", "minesLikeZone" : 5, "treasureLikeZone" : 4 }, + "7" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : 2, "monsters" : "normal", "minesLikeZone" : 4, "treasureLikeZone" : 4 }, + "8" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : 3, "monsters" : "normal", "minesLikeZone" : 4, "treasureLikeZone" : 4 }, + "9" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : 3, "monsters" : "normal", "minesLikeZone" : 5, "treasureLikeZone" : 4 }, + "10" : { "type" : "treasure", "size" : 1, "neutralTowns" : { "towns" : 1 }, + "monsters" : "strong", + "mines" : {"gold" : 1}, + "treasure" : [ + {"min" : 21000, "max": 25000, "density" : 3}, + {"min" : 10000, "max": 21000, "density" : 10} + ]}, + "11" : { "type" : "treasure", "size" : 1, "neutralTowns" : { "towns" : 1 }, "monsters" : "strong", "minesLikeZone" : 10, "treasureLikeZone" : 10 }, + "12" : { "type" : "treasure", "size" : 1, "neutralTowns" : { "towns" : 1 }, "monsters" : "strong", "minesLikeZone" : 10, "treasureLikeZone" : 10 } }, "connections" : [ - { "a" : "1", "b" : "4", "guard" : 2000 }, - { "a" : "1", "b" : "5", "guard" : 2000 }, - { "a" : "2", "b" : "6", "guard" : 2000 }, - { "a" : "2", "b" : "7", "guard" : 2000 }, - { "a" : "3", "b" : "8", "guard" : 2000 }, - { "a" : "3", "b" : "9", "guard" : 2000 }, + { "a" : "1", "b" : "4", "guard" : 2500 }, + { "a" : "1", "b" : "5", "guard" : 2500 }, + { "a" : "2", "b" : "6", "guard" : 2500 }, + { "a" : "2", "b" : "7", "guard" : 2500 }, + { "a" : "3", "b" : "8", "guard" : 2500 }, + { "a" : "3", "b" : "9", "guard" : 2500 }, { "a" : "4", "b" : "10", "guard" : 20000 }, { "a" : "5", "b" : "12", "guard" : 20000 }, { "a" : "6", "b" : "10", "guard" : 20000 }, @@ -134,25 +207,29 @@ "1" : { "type" : "playerStart", "size" : 2, "owner" : 1, - "playerTowns" : { "castles" : 1 } + "playerTowns" : { "castles" : 1 }, + "monsters" : "normal", }, "2" : { "type" : "playerStart", "size" : 2, "owner" : 2, - "playerTowns" : { "castles" : 1 } + "playerTowns" : { "castles" : 1 }, + "monsters" : "normal", }, "3" : { "type" : "cpuStart", "size" : 3, "owner" : 3, - "playerTowns" : { "castles" : 1 } + "playerTowns" : { "castles" : 1 }, + "monsters" : "weak", }, "4" : { "type" : "cpuStart", "size" : 3, "owner" : 4, - "playerTowns" : { "castles" : 1 } + "playerTowns" : { "castles" : 1 }, + "monsters" : "weak", }, - "5" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : "3" }, - "6" : { "type" : "treasure", "size" : 1, "terrainTypeLikeZone" : "4" } + "5" : { "type" : "treasure", "size" : 1, "monsters" : "strong", "terrainTypeLikeZone" : 3 }, + "6" : { "type" : "treasure", "size" : 1, "monsters" : "strong", "terrainTypeLikeZone" : 4 } }, "connections" : [ @@ -163,5 +240,73 @@ { "a" : "3", "b" : "5", "guard" : 2000 }, { "a" : "4", "b" : "6", "guard" : 2000 } ] + }, + "Jebus Cross": + { + "minSize" : "l", "maxSize" : "xl", + "players" : "4", + "zones": + { + "1": + { + "type" : "playerStart", "size" : 30, "owner" : 1, + "playerTowns" : { "castles" : 1 }, + "neutralTowns" : { "towns" : 2 }, + "monsters" : "weak", + "mines" : {"wood" : 4, "ore" : 4, "gems" : 1, "crystal" : 1, "sulfur" : 1, "mercury" : 1, "gold" : 2}, + "treasure" : [ + {"min" : 12000, "max": 22000, "density": 1}, + {"min" : 500, "max": 1600, "density": 6}, + {"min" : 300, "max": 3000, "density": 14} + ] + }, + "2": + { + "type" : "playerStart", "size" : 30, "owner" : 2, + "playerTowns" : { "castles" : 1 }, + "neutralTowns" : { "towns" : 2 }, + "monsters" : "weak", + "minesLikeZone" : 1, + "treasureLikeZone" : 1 + }, + "3": + { + "type" : "playerStart", "size" : 30, "owner" : 3, + "playerTowns" : { "castles" : 1 }, + "neutralTowns" : { "towns" : 2 }, + "monsters" : "weak", + "minesLikeZone" : 1, + "treasureLikeZone" : 1 + }, + "4": + { + "type" : "playerStart", "size" : 30, "owner" : 4, + "playerTowns" : { "castles" : 1 }, + "neutralTowns" : { "towns" : 2 }, + "monsters" : "weak", + "minesLikeZone" : 1, + "treasureLikeZone" : 1 + }, + "5" : + { + "type" : "treasure", "size" : 40, + "neutralTowns" : { "castles" : 2 }, + "terrainTypes" : [ "sand" ], "matchTerrainToTown" : false, + "monsters" : "strong", + "mines" : {"gold" : 4}, + "treasure" : [ + {"min" : 35000, "max": 55000, "density" : 3}, + {"min" : 25000, "max": 35000, "density" : 10}, + {"min" : 10000, "max": 25000, "density" : 10} + ] + } + }, + "connections" : + [ + { "a" : "1", "b" : "5", "guard" : 45000 }, + { "a" : "2", "b" : "5", "guard" : 45000 }, + { "a" : "3", "b" : "5", "guard" : 45000 }, + { "a" : "4", "b" : "5", "guard" : 45000 } + ] } } diff --git a/config/schemas/faction.json b/config/schemas/faction.json index 3684ae185..5607eaa76 100644 --- a/config/schemas/faction.json +++ b/config/schemas/faction.json @@ -104,46 +104,16 @@ "type":"object", "additionalProperties" : false, "required" : [ - "adventureMap", "buildingsIcons", "buildings", "creatures", "guildWindow", "names", + "mapObject", "buildingsIcons", "buildings", "creatures", "guildWindow", "names", "hallBackground", "hallSlots", "horde", "mageGuild", "moatDamage", "defaultTavern", "tavernVideo", "guildBackground", "musicTheme", "siege", "structures", "townBackground", "warMachine" ], "description": "town", "properties":{ - "adventureMap": { - "type":"object", - "additionalProperties" : false, - "description": "Paths to images of object on adventure map", - "required" : [ "capitol", "castle", "village" ], - "properties":{ - "capitol": { - "type":"string", - "description": "Town with capitol", - "format" : "defFile" - }, - "castle": { - "type":"string", - "description": "Town with built fort", - "format" : "defFile" - }, - "village": { - "type":"string", - "description": "Village without built fort", - "format" : "defFile" - }, - "dwellings" : { - "type" : "array", - "minItems" : 7, - "maxItems" : 7, - "description" : "Dwellings on adventure map", - "items" : { - "type" : "object", - "additionalProperties" : false, - "required" : [ "name", "graphics" ], - "properties" : { - "name": { "type":"string" }, - "graphics": { "type":"string", "format" : "defFile" } - } - } + "mapObject" : { + "properties" : { + "filters" : { + "type" : "object", + "additionalProperties" : { "type" : "array" } } } }, diff --git a/config/schemas/heroClass.json b/config/schemas/heroClass.json index 59bf42529..d10731694 100644 --- a/config/schemas/heroClass.json +++ b/config/schemas/heroClass.json @@ -6,7 +6,7 @@ "required" : [ "animation", "faction", "highLevelChance", "lowLevelChance", "name", "primarySkills", "secondarySkills", "tavern", "defaultTavern", - "affinity", "commander" + "affinity", "commander", "mapObject" ], "additionalProperties" : false, @@ -15,7 +15,7 @@ "type":"object", "additionalProperties" : false, "description": "Files related to hero animation", - "required": [ "battle", "map" ], + "required": [ "battle" ], "properties":{ "battle": { "type":"object", @@ -34,24 +34,14 @@ "format" : "defFile" } } - }, - "map": { - "type":"object", - "additionalProperties" : false, - "description": "Hero animations for adventure map", - "required": [ "female", "male" ], - "properties":{ - "female": { - "type":"string", - "description": "Female version. Warning: not implemented!", - "format" : "defFile" - }, - "male": { - "type":"string", - "description": "Male version", - "format" : "defFile" - } - } + } + } + }, + "mapObject" : { + "properties" : { + "filters" : { + "type" : "object", + "additionalProperties" : { "type" : "array" } } } }, diff --git a/config/schemas/object.json b/config/schemas/object.json new file mode 100644 index 000000000..82e743798 --- /dev/null +++ b/config/schemas/object.json @@ -0,0 +1,31 @@ +{ + "type":"object", + "$schema": "http://json-schema.org/draft-04/schema", + "title" : "VCMI map object format", + "description" : "Description of map object class", + "required": [ "handler", "name" ], + "additionalProperties" : false, + + "properties":{ + "index": { + "type":"number", + }, + "name": { + "type":"string", + }, + + "handler": { + "type":"string", + }, + + "base": { + "type" : "object" + }, + "types": { + "type":"object", + "additionalProperties": { + "$ref" : "vcmi:objectType" + } + } + } +} diff --git a/config/schemas/objectTemplate.json b/config/schemas/objectTemplate.json index 649262ba7..bd66f1922 100644 --- a/config/schemas/objectTemplate.json +++ b/config/schemas/objectTemplate.json @@ -3,18 +3,10 @@ "$schema": "http://json-schema.org/draft-04/schema", "title" : "VCMI map object template format", "description" : "Description of map object tempate that describes appearence of object instance", - "required": ["basebase", "base", "animation", "mask" ], + "required": [ "animation", "mask" ], "additionalProperties" : false, "properties":{ - "basebase": { - "type" : "number", - "description": "Base object type, e.g. town or hero" - }, - "base": { - "type" : "number", - "description": "Object subtype, e.g. Castle, Rampart, Cleric, Demon" - }, "animation": { "type" : "string", "description": "Path to def file with animation of this object", diff --git a/config/schemas/objectType.json b/config/schemas/objectType.json new file mode 100644 index 000000000..8a704735f --- /dev/null +++ b/config/schemas/objectType.json @@ -0,0 +1,27 @@ +{ + "type":"object", + "$schema": "http://json-schema.org/draft-04/schema", + "title" : "VCMI map object type format", + "description" : "Description of map object type, used only as sub-schema of object", + "required": [ ], + "additionalProperties" : true, // may have type-dependant properties + + "properties":{ + "index": { + "type":"number", + }, + "name": { + "type":"string", + }, + + "base": { + "type" : "object" + }, + "templates": { + "type":"object", + "additionalProperties": { + "$ref" : "vcmi:objectTemplate" + } + } + } +} diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index ee392e608..8b8395157 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -13,7 +13,7 @@ #include #include "VCMI_Lib.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "CHeroHandler.h" #include "CCreatureHandler.h" #include "CSpellHandler.h" diff --git a/lib/BattleState.h b/lib/BattleState.h index f62490052..3d8ac4587 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -4,12 +4,14 @@ #include "BattleHex.h" #include "HeroBonus.h" #include "CCreatureSet.h" -#include "CObjectHandler.h" +#include "mapObjects/CGTownInstance.h" +#include "mapObjects/CGHeroInstance.h" #include "CCreatureHandler.h" #include "CObstacleInstance.h" #include "ConstTransitivePtr.h" #include "GameConstants.h" #include "CBattleCallback.h" +#include "int3.h" /* * BattleState.h, part of VCMI engine diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index f7c5393f8..561300a7b 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -16,11 +16,13 @@ #include "VCMI_Lib.h" #include "CModHandler.h" #include "CSpellHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/MapObjects.h" #include "NetPacksBase.h" #include "GameConstants.h" #include "CRandomGenerator.h" +#include "mapObjects/CObjectClassesHandler.h" + using namespace boost::assign; // Note: list must match entries in ArtTraits.txt @@ -260,12 +262,21 @@ CArtifact * CArtHandler::loadFromJson(const JsonNode & node) return art; } -void CArtHandler::addSlot(CArtifact * art, const std::string & slotID) +ArtifactPosition CArtHandler::stringToSlot(std::string slotName) { #define ART_POS(x) ( #x, ArtifactPosition::x ) static const std::map artifactPositionMap = boost::assign::map_list_of ART_POS_LIST; #undef ART_POS + auto it = artifactPositionMap.find (slotName); + if (it != artifactPositionMap.end()) + return it->second; + logGlobal->warnStream() << "Warning! Artifact slot " << slotName << " not recognized!"; + return ArtifactPosition::PRE_FIRST; +} + +void CArtHandler::addSlot(CArtifact * art, const std::string & slotID) +{ if (slotID == "MISC") { art->possibleSlots[ArtBearer::HERO] += ArtifactPosition::MISC1, ArtifactPosition::MISC2, ArtifactPosition::MISC3, ArtifactPosition::MISC4, ArtifactPosition::MISC5; @@ -276,14 +287,9 @@ void CArtHandler::addSlot(CArtifact * art, const std::string & slotID) } else { - auto it = artifactPositionMap.find (slotID); - if (it != artifactPositionMap.end()) - { - auto slot = it->second; + auto slot = stringToSlot(slotID); + if (slot != ArtifactPosition::PRE_FIRST) art->possibleSlots[ArtBearer::HERO].push_back (slot); - } - else - logGlobal->warnStream() << "Warning! Artifact slot " << slotID << " not recognized!"; } } @@ -301,7 +307,7 @@ void CArtHandler::loadSlots(CArtifact * art, const JsonNode & node) } } -void CArtHandler::loadClass(CArtifact * art, const JsonNode & node) +CArtifact::EartClass CArtHandler::stringToClass(std::string className) { static const std::map artifactClassMap = boost::assign::map_list_of ("TREASURE", CArtifact::ART_TREASURE) @@ -310,16 +316,17 @@ void CArtHandler::loadClass(CArtifact * art, const JsonNode & node) ("RELIC", CArtifact::ART_RELIC) ("SPECIAL", CArtifact::ART_SPECIAL); - auto it = artifactClassMap.find (node["class"].String()); + auto it = artifactClassMap.find (className); if (it != artifactClassMap.end()) - { - art->aClass = it->second; - } - else - { - logGlobal->warnStream() << "Warning! Artifact rarity " << node["class"].String() << " not recognized!"; - art->aClass = CArtifact::ART_SPECIAL; - } + return it->second; + + logGlobal->warnStream() << "Warning! Artifact rarity " << className << " not recognized!"; + return CArtifact::ART_SPECIAL; +} + +void CArtHandler::loadClass(CArtifact * art, const JsonNode & node) +{ + art->aClass = stringToClass(node["class"].String()); } void CArtHandler::loadType(CArtifact * art, const JsonNode & node) @@ -422,7 +429,7 @@ CreatureID CArtHandler::machineIDToCreature(ArtifactID id) return CreatureID::NONE; //this artifact is not a creature } -ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags) +ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags, std::function accepts) { auto getAllowedArts = [&](std::vector > &out, std::vector *arts, CArtifact::EartClass flag) { @@ -431,8 +438,11 @@ ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags) for (auto & arts_i : *arts) { - CArtifact *art = arts_i; - out.push_back(art); + if (accepts(arts_i->id)) + { + CArtifact *art = arts_i; + out.push_back(art); + } } }; @@ -467,6 +477,16 @@ ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags) return artID; } +ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, std::function accepts) +{ + return pickRandomArtifact(rand, 0xff, accepts); +} + +ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags) +{ + return pickRandomArtifact(rand, flags, [](ArtifactID){ return true;}); +} + Bonus *createBonus(Bonus::BonusType type, int val, int subtype, Bonus::ValueType valType, shared_ptr limiter = shared_ptr(), int additionalInfo = 0) { auto added = new Bonus(Bonus::PERMANENT,type,Bonus::ARTIFACT,val,-1,subtype); @@ -647,20 +667,22 @@ void CArtHandler::afterLoadFinalization() } } - //Note: "10" is used here because H3 text files don't define any template for art with ID 0 - ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::ARTIFACT, 10).front(); for (CArtifact * art : artifacts) { + VLC->objtypeh->loadSubObject(art->Name(), JsonNode(), Obj::ARTIFACT, art->id.num); + if (!art->advMapDef.empty()) { - base.animationFile = art->advMapDef; - base.subid = art->id; + JsonNode templ; + templ["animation"].String() = art->advMapDef; - // replace existing (if any) and add new template. + // add new template. // Necessary for objects added via mods that don't have any templates in H3 - VLC->dobjinfo->eraseAll(Obj::ARTIFACT, art->id); - VLC->dobjinfo->registerTemplate(base); + VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->id)->addTemplate(templ); } + // object does not have any templates - this is not usable object (e.g. pseudo-art like lock) + if (VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->id)->getTemplates().empty()) + VLC->objtypeh->removeSubObject(Obj::ARTIFACT, art->id); } } diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index 1105f9d4a..592e1de58 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -203,11 +203,17 @@ public: boost::optional&> listFromClass(CArtifact::EartClass artifactClass); + ArtifactPosition stringToSlot(std::string slotName); + CArtifact::EartClass stringToClass(std::string className); + /// Gets a artifact ID randomly and removes the selected artifact from this handler. ArtifactID pickRandomArtifact(CRandomGenerator & rand, int flags); + ArtifactID pickRandomArtifact(CRandomGenerator & rand, std::function accepts); + ArtifactID pickRandomArtifact(CRandomGenerator & rand, int flags, std::function accepts); + bool legalArtifact(ArtifactID id); - void getAllowedArts(std::vector > &out, std::vector *arts, int flag); - void getAllowed(std::vector > &out, int flags); + //void getAllowedArts(std::vector > &out, std::vector *arts, int flag); + //void getAllowed(std::vector > &out, int flags); bool isBigArtifact (ArtifactID artID) const {return bigArtifacts.find(artID) != bigArtifacts.end();} void initAllowedArtifactsList(const std::vector &allowed); //allowed[art_id] -> 0 if not allowed, 1 if allowed static ArtifactID creatureToMachineID(CreatureID id); diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index dfba235b8..f4a1aa2f3 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -9,6 +9,8 @@ #include "CModHandler.h" #include "StringConstants.h" +#include "mapObjects/CObjectClassesHandler.h" + using namespace boost::assign; /* @@ -1114,19 +1116,19 @@ void CCreatureHandler::buildBonusTreeForTiers() void CCreatureHandler::afterLoadFinalization() { - ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::MONSTER, 0).front(); for (CCreature * crea : creatures) { + VLC->objtypeh->loadSubObject(crea->nameSing, JsonNode(), Obj::MONSTER, crea->idNumber.num); if (!crea->advMapDef.empty()) { - base.animationFile = crea->advMapDef; - base.subid = crea->idNumber; - - // replace existing (if any) and add new template. - // Necessary for objects added via mods that don't have any templates in H3 - VLC->dobjinfo->eraseAll(Obj::MONSTER, crea->idNumber); - VLC->dobjinfo->registerTemplate(base); + JsonNode templ; + templ["animation"].String() = crea->advMapDef; + VLC->objtypeh->getHandlerFor(Obj::MONSTER, crea->idNumber)->addTemplate(templ); } + + // object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower) + if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, crea->idNumber.num)->getTemplates().empty()) + VLC->objtypeh->removeSubObject(Obj::MONSTER, crea->idNumber.num); } } diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 3a1d85072..f33019a81 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -4,7 +4,7 @@ #include "CCreatureHandler.h" #include "VCMI_Lib.h" #include "CModHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/CGHeroInstance.h" #include "IGameCallback.h" #include "CGameState.h" #include "CGeneralTextHandler.h" diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 39da01b9a..eb62696ff 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -12,7 +12,7 @@ #include "CGameInfoCallback.h" #include "CGameState.h" // PlayerState -#include "CObjectHandler.h" // for CGObjectInstance +#include "mapObjects/CObjectHandler.h" // for CGObjectInstance #include "StartInfo.h" // for StartInfo #include "BattleState.h" // for BattleInfo #include "NetPacks.h" // for InfoWindow diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index cd94b763d..45b1510a0 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1,3594 +1,3569 @@ -#include "StdInc.h" -#include "CGameState.h" - -#include "mapping/CCampaignHandler.h" -#include "CDefObjInfoHandler.h" -#include "CArtHandler.h" -#include "CBuildingHandler.h" -#include "CGeneralTextHandler.h" -#include "CTownHandler.h" -#include "CSpellHandler.h" -#include "CHeroHandler.h" -#include "CObjectHandler.h" -#include "CCreatureHandler.h" -#include "CModHandler.h" -#include "VCMI_Lib.h" -#include "Connection.h" -#include "mapping/CMap.h" -#include "mapping/CMapService.h" -#include "StartInfo.h" -#include "NetPacks.h" -#include "registerTypes/RegisterTypes.h" -#include "mapping/CMapInfo.h" -#include "BattleState.h" -#include "JsonNode.h" -#include "filesystem/Filesystem.h" -#include "GameConstants.h" -#include "rmg/CMapGenerator.h" -#include "CStopWatch.h" -#include "mapping/CMapEditManager.h" - -class CGObjectInstance; - -#ifdef min -#undef min -#endif -#ifdef max -#undef max -#endif - -/* - * CGameState.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -template class CApplyOnGS; - -class CBaseForGSApply -{ -public: - virtual void applyOnGS(CGameState *gs, void *pack) const =0; - virtual ~CBaseForGSApply(){}; - template static CBaseForGSApply *getApplier(const U * t=nullptr) - { - return new CApplyOnGS; - } -}; - -template class CApplyOnGS : public CBaseForGSApply -{ -public: - void applyOnGS(CGameState *gs, void *pack) const - { - T *ptr = static_cast(pack); - - boost::unique_lock lock(*gs->mx); - ptr->applyGs(gs); - } -}; - -static CApplier *applierGs = nullptr; - -// class IObjectCaller -// { -// public: -// virtual ~IObjectCaller(){}; -// virtual void preInit()=0; -// virtual void postInit()=0; -// }; -// -// template -// class CObjectCaller : public IObjectCaller -// { -// public: -// void preInit() -// { -// //T::preInit(); -// } -// void postInit() -// { -// //T::postInit(); -// } -// }; - -// class CObjectCallersHandler -// { -// public: -// std::vector apps; -// -// template void registerType(const T * t=nullptr) -// { -// apps.push_back(new CObjectCaller); -// } -// -// CObjectCallersHandler() -// { -// registerTypesMapObjects(*this); -// } -// -// ~CObjectCallersHandler() -// { -// for (auto & elem : apps) -// delete elem; -// } -// -// void preInit() -// { -// // for (size_t i = 0; i < apps.size(); i++) -// // apps[i]->preInit(); -// } -// -// void postInit() -// { -// //for (size_t i = 0; i < apps.size(); i++) -// //apps[i]->postInit(); -// } -// } *objCaller = nullptr; - -void MetaString::getLocalString(const std::pair &txt, std::string &dst) const -{ - int type = txt.first, ser = txt.second; - - if(type == ART_NAMES) - { - dst = VLC->arth->artifacts[ser]->Name(); - } - else if(type == CRE_PL_NAMES) - { - dst = VLC->creh->creatures[ser]->namePl; - } - else if(type == MINE_NAMES) - { - dst = VLC->generaltexth->mines[ser].first; - } - else if(type == MINE_EVNTS) - { - dst = VLC->generaltexth->mines[ser].second; - } - else if(type == SPELL_NAME) - { - dst = SpellID(ser).toSpell()->name; - } - else if(type == CRE_SING_NAMES) - { - dst = VLC->creh->creatures[ser]->nameSing; - } - else if(type == ART_DESCR) - { - dst = VLC->arth->artifacts[ser]->Description(); - } - else if (type == ART_EVNTS) - { - dst = VLC->arth->artifacts[ser]->EventText(); - } - else - { - std::vector *vec; - switch(type) - { - case GENERAL_TXT: - vec = &VLC->generaltexth->allTexts; - break; - case XTRAINFO_TXT: - vec = &VLC->generaltexth->xtrainfo; - break; - case OBJ_NAMES: - vec = &VLC->generaltexth->names; - break; - case RES_NAMES: - vec = &VLC->generaltexth->restypes; - break; - case ARRAY_TXT: - vec = &VLC->generaltexth->arraytxt; - break; - case CREGENS: - vec = &VLC->generaltexth->creGens; - break; - case CREGENS4: - vec = &VLC->generaltexth->creGens4; - break; - case ADVOB_TXT: - vec = &VLC->generaltexth->advobtxt; - break; - case SEC_SKILL_NAME: - vec = &VLC->generaltexth->skillName; - break; - case COLOR: - vec = &VLC->generaltexth->capColors; - break; - default: - logGlobal->errorStream() << "Failed string substitution because type is " << type; - dst = "#@#"; - return; - } - if(vec->size() <= ser) - { - logGlobal->errorStream() << "Failed string substitution with type " << type << " because index " << ser << " is out of bounds!"; - dst = "#!#"; - } - else - dst = (*vec)[ser]; - } -} - -DLL_LINKAGE void MetaString::toString(std::string &dst) const -{ - size_t exSt = 0, loSt = 0, nums = 0; - dst.clear(); - - for(auto & elem : message) - {//TEXACT_STRING, TLOCAL_STRING, TNUMBER, TREPLACE_ESTRING, TREPLACE_LSTRING, TREPLACE_NUMBER - switch(elem) - { - case TEXACT_STRING: - dst += exactStrings[exSt++]; - break; - case TLOCAL_STRING: - { - std::string hlp; - getLocalString(localStrings[loSt++], hlp); - dst += hlp; - } - break; - case TNUMBER: - dst += boost::lexical_cast(numbers[nums++]); - break; - case TREPLACE_ESTRING: - boost::replace_first(dst, "%s", exactStrings[exSt++]); - break; - case TREPLACE_LSTRING: - { - std::string hlp; - getLocalString(localStrings[loSt++], hlp); - boost::replace_first(dst, "%s", hlp); - } - break; - case TREPLACE_NUMBER: - boost::replace_first(dst, "%d", boost::lexical_cast(numbers[nums++])); - break; - case TREPLACE_PLUSNUMBER: - boost::replace_first(dst, "%+d", '+' + boost::lexical_cast(numbers[nums++])); - break; - default: - logGlobal->errorStream() << "MetaString processing error!"; - break; - } - } -} - -DLL_LINKAGE std::string MetaString::toString() const -{ - std::string ret; - toString(ret); - return ret; -} - -DLL_LINKAGE std::string MetaString::buildList () const -///used to handle loot from creature bank -{ - - size_t exSt = 0, loSt = 0, nums = 0; - std::string lista; - for (int i = 0; i < message.size(); ++i) - { - if (i > 0 && (message[i] == TEXACT_STRING || message[i] == TLOCAL_STRING)) - { - if (exSt == exactStrings.size() - 1) - lista += VLC->generaltexth->allTexts[141]; //" and " - else - lista += ", "; - } - switch (message[i]) - { - case TEXACT_STRING: - lista += exactStrings[exSt++]; - break; - case TLOCAL_STRING: - { - std::string hlp; - getLocalString (localStrings[loSt++], hlp); - lista += hlp; - } - break; - case TNUMBER: - lista += boost::lexical_cast(numbers[nums++]); - break; - case TREPLACE_ESTRING: - lista.replace (lista.find("%s"), 2, exactStrings[exSt++]); - break; - case TREPLACE_LSTRING: - { - std::string hlp; - getLocalString (localStrings[loSt++], hlp); - lista.replace (lista.find("%s"), 2, hlp); - } - break; - case TREPLACE_NUMBER: - lista.replace (lista.find("%d"), 2, boost::lexical_cast(numbers[nums++])); - break; - default: - logGlobal->errorStream() << "MetaString processing error!"; - } - - } - return lista; -} - - -void MetaString::addCreReplacement(CreatureID id, TQuantity count) //adds sing or plural name; -{ - if (!count) - addReplacement (CRE_PL_NAMES, id); //no creatures - just empty name (eg. defeat Angels) - else if (count == 1) - addReplacement (CRE_SING_NAMES, id); - else - addReplacement (CRE_PL_NAMES, id); -} - -void MetaString::addReplacement(const CStackBasicDescriptor &stack) -{ - assert(stack.type); //valid type - addCreReplacement(stack.type->idNumber, stack.count); -} - -static CGObjectInstance * createObject(Obj id, int subid, int3 pos, PlayerColor owner) -{ - CGObjectInstance * nobj; - switch(id) - { - case Obj::HERO: - nobj = new CGHeroInstance(); - nobj->appearance = VLC->dobjinfo->pickCandidates(id, VLC->heroh->heroes[subid]->heroClass->id).front(); - break; - case Obj::TOWN: - nobj = new CGTownInstance; - break; - default: //rest of objects - nobj = new CGObjectInstance; - break; - } - nobj->ID = id; - nobj->subID = subid; - nobj->pos = pos; - nobj->tempOwner = owner; - if (id != Obj::HERO) - nobj->appearance = VLC->dobjinfo->pickCandidates(id, subid).front(); - - return nobj; -} - -CGHeroInstance * CGameState::HeroesPool::pickHeroFor(bool native, PlayerColor player, const CTown *town, - std::map > &available, CRandomGenerator & rand, const CHeroClass * bannedClass /*= nullptr*/) const -{ - CGHeroInstance *ret = nullptr; - - if(player>=PlayerColor::PLAYER_LIMIT) - { - logGlobal->errorStream() << "Cannot pick hero for " << town->faction->index << ". Wrong owner!"; - return nullptr; - } - - std::vector pool; - - if(native) - { - for(auto & elem : available) - { - if(pavailable.find(elem.first)->second & 1<type->heroClass->faction == town->faction->index) - { - pool.push_back(elem.second); //get all available heroes - } - } - if(!pool.size()) - { - logGlobal->errorStream() << "Cannot pick native hero for " << player << ". Picking any..."; - return pickHeroFor(false, player, town, available, rand); - } - else - { - ret = *RandomGeneratorUtil::nextItem(pool, rand); - } - } - else - { - int sum=0, r; - - for(auto & elem : available) - { - if (pavailable.find(elem.first)->second & (1<type->heroClass != bannedClass) ) // and his class is not same as other hero - { - pool.push_back(elem.second); - sum += elem.second->type->heroClass->selectionProbability[town->faction->index]; //total weight - } - } - if(!pool.size() || sum == 0) - { - logGlobal->errorStream() << "There are no heroes available for player " << player<<"!"; - return nullptr; - } - - r = rand.nextInt(sum - 1); - for (auto & elem : pool) - { - r -= elem->type->heroClass->selectionProbability[town->faction->index]; - if(r < 0) - { - ret = elem; - break; - } - } - if(!ret) - ret = pool.back(); - } - - available.erase(ret->subID); - return ret; -} - -void CGameState::CrossoverHeroesList::addHeroToBothLists(CGHeroInstance * hero) -{ - heroesFromPreviousScenario.push_back(hero); - heroesFromAnyPreviousScenarios.push_back(hero); -} - -void CGameState::CrossoverHeroesList::removeHeroFromBothLists(CGHeroInstance * hero) -{ - heroesFromPreviousScenario -= hero; - heroesFromAnyPreviousScenarios -= hero; -} - -CGameState::CampaignHeroReplacement::CampaignHeroReplacement(CGHeroInstance * hero, ObjectInstanceID heroPlaceholderId) : hero(hero), heroPlaceholderId(heroPlaceholderId) -{ - -} - -int CGameState::pickNextHeroType(PlayerColor owner) -{ - const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner); - if(ps.hero >= 0 && !isUsedHero(HeroTypeID(ps.hero))) //we haven't used selected hero - { - return ps.hero; - } - - return pickUnusedHeroTypeRandomly(owner); -} - -int CGameState::pickUnusedHeroTypeRandomly(PlayerColor owner) -{ - //list of available heroes for this faction and others - std::vector factionHeroes, otherHeroes; - - const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner); - for(HeroTypeID hid : getUnusedAllowedHeroes()) - { - if(VLC->heroh->heroes[hid.getNum()]->heroClass->faction == ps.castle) - factionHeroes.push_back(hid); - else - otherHeroes.push_back(hid); - } - - // select random hero native to "our" faction - if(!factionHeroes.empty()) - { - return RandomGeneratorUtil::nextItem(factionHeroes, rand)->getNum(); - } - - logGlobal->warnStream() << "Cannot find free hero of appropriate faction for player " << owner << " - trying to get first available..."; - if(!otherHeroes.empty()) - { - return RandomGeneratorUtil::nextItem(otherHeroes, rand)->getNum(); - } - - logGlobal->errorStream() << "No free allowed heroes!"; - auto notAllowedHeroesButStillBetterThanCrash = getUnusedAllowedHeroes(true); - if(notAllowedHeroesButStillBetterThanCrash.size()) - return notAllowedHeroesButStillBetterThanCrash.begin()->getNum(); - - logGlobal->errorStream() << "No free heroes at all!"; - assert(0); //current code can't handle this situation - return -1; // no available heroes at all -} - -std::pair CGameState::pickObject (CGObjectInstance *obj) -{ - switch(obj->ID) - { - case Obj::RANDOM_ART: - return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE | CArtifact::ART_MINOR | CArtifact::ART_MAJOR | CArtifact::ART_RELIC)); - case Obj::RANDOM_TREASURE_ART: - return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE)); - case Obj::RANDOM_MINOR_ART: - return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MINOR)); - case Obj::RANDOM_MAJOR_ART: - return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MAJOR)); - case Obj::RANDOM_RELIC_ART: - return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_RELIC)); - case Obj::RANDOM_HERO: - return std::make_pair(Obj::HERO, pickNextHeroType(obj->tempOwner)); - case Obj::RANDOM_MONSTER: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand)); - case Obj::RANDOM_MONSTER_L1: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 1)); - case Obj::RANDOM_MONSTER_L2: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 2)); - case Obj::RANDOM_MONSTER_L3: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 3)); - case Obj::RANDOM_MONSTER_L4: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 4)); - case Obj::RANDOM_RESOURCE: - return std::make_pair(Obj::RESOURCE,rand.nextInt(6)); //now it's OH3 style, use %8 for mithril - case Obj::RANDOM_TOWN: - { - PlayerColor align = PlayerColor((static_cast(obj))->alignment); - si32 f; // can be negative (for random) - if(align >= PlayerColor::PLAYER_LIMIT)//same as owner / random - { - if(obj->tempOwner >= PlayerColor::PLAYER_LIMIT) - f = -1; //random - else - f = scenarioOps->getIthPlayersSettings(obj->tempOwner).castle; - } - else - { - f = scenarioOps->getIthPlayersSettings(align).castle; - } - if(f<0) - { - do - { - f = rand.nextInt(VLC->townh->factions.size() - 1); - } - while (VLC->townh->factions[f]->town == nullptr); // find playable faction - } - return std::make_pair(Obj::TOWN,f); - } - case Obj::RANDOM_MONSTER_L5: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 5)); - case Obj::RANDOM_MONSTER_L6: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 6)); - case Obj::RANDOM_MONSTER_L7: - return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 7)); - case Obj::RANDOM_DWELLING: - case Obj::RANDOM_DWELLING_LVL: - case Obj::RANDOM_DWELLING_FACTION: - { - CGDwelling * dwl = static_cast(obj); - int faction; - - //if castle alignment available - if (auto info = dynamic_cast(dwl->info)) - { - faction = rand.nextInt(VLC->townh->factions.size() - 1); - if (info->asCastle) - { - for(auto & elem : map->objects) - { - if(!elem) - continue; - - if(elem->ID==Obj::RANDOM_TOWN - && dynamic_cast(elem.get())->identifier == info->identifier) - { - randomizeObject(elem); //we have to randomize the castle first - faction = elem->subID; - break; - } - else if(elem->ID==Obj::TOWN - && dynamic_cast(elem.get())->identifier == info->identifier) - { - faction = elem->subID; - break; - } - } - } - else - { - while(!(info->castles[0]&(1<7) && (info->castles[1]&(1<<(faction-8)))) - break; - faction = rand.nextInt(GameConstants::F_NUMBER - 1); - } - } - } - else // castle alignment fixed - faction = obj->subID; - - int level; - - //if level set to range - if (auto info = dynamic_cast(dwl->info)) - { - level = rand.nextInt(info->minLevel, info->maxLevel); - } - else // fixed level - { - level = obj->subID; - } - - delete dwl->info; - dwl->info = nullptr; - - std::pair result(Obj::NO_OBJ, -1); - CreatureID cid = VLC->townh->factions[faction]->town->creatures[level][0]; - - //golem factory is not in list of cregens but can be placed as random object - static const CreatureID factoryCreatures[] = {CreatureID::STONE_GOLEM, CreatureID::IRON_GOLEM, - CreatureID::GOLD_GOLEM, CreatureID::DIAMOND_GOLEM}; - std::vector factory(factoryCreatures, factoryCreatures + ARRAY_COUNT(factoryCreatures)); - if (vstd::contains(factory, cid)) - result = std::make_pair(Obj::CREATURE_GENERATOR4, 1); - - //NOTE: this will pick last dwelling with this creature (Mantis #900) - //check for block map equality is better but more complex solution - for(auto &iter : VLC->objh->cregens) - if (iter.second == cid) - result = std::make_pair(Obj::CREATURE_GENERATOR1, iter.first); - - if (result.first == Obj::NO_OBJ) - { - logGlobal->errorStream() << "Error: failed to find creature for dwelling of "<< int(faction) << " of level " << int(level); - result = std::make_pair(Obj::CREATURE_GENERATOR1, RandomGeneratorUtil::nextItem(VLC->objh->cregens, rand)->first); - } - - return result; - } - } - return std::make_pair(Obj::NO_OBJ,-1); -} - -void CGameState::randomizeObject(CGObjectInstance *cur) -{ - std::pair ran = pickObject(cur); - if(ran.first == Obj::NO_OBJ || ran.second<0) //this is not a random object, or we couldn't find anything - { - if(cur->ID==Obj::TOWN) //town - set def - { - const TerrainTile &tile = map->getTile(cur->visitablePos()); - CGTownInstance *t = dynamic_cast(cur); - t->town = VLC->townh->factions[t->subID]->town; - t->appearance = VLC->dobjinfo->pickCandidates(Obj::TOWN, t->subID, tile.terType).front(); - t->updateAppearance(); - } - return; - } - else if(ran.first==Obj::HERO)//special code for hero - { - CGHeroInstance *h = dynamic_cast(cur); - if(!h) {logGlobal->warnStream()<<"Wrong random hero at "<pos; return;} - cur->ID = ran.first; - cur->subID = ran.second; - h->type = VLC->heroh->heroes[ran.second]; - h->portrait = h->type->imageIndex; - h->randomizeArmy(h->type->heroClass->faction); - map->heroesOnMap.push_back(h); - return; //TODO: maybe we should do something with definfo? - } - else if(ran.first==Obj::TOWN)//special code for town - { - const TerrainTile &tile = map->getTile(cur->visitablePos()); - CGTownInstance *t = dynamic_cast(cur); - if(!t) {logGlobal->warnStream()<<"Wrong random town at "<pos; return;} - cur->ID = ran.first; - cur->subID = ran.second; - //FIXME: copy-pasted from above - t->town = VLC->townh->factions[t->subID]->town; - t->appearance = VLC->dobjinfo->pickCandidates(Obj::TOWN,t->subID, tile.terType).front(); - t->updateAppearance(); - - t->randomizeArmy(t->subID); - map->towns.push_back(t); - return; - } - else - { - if (ran.first != cur->appearance.id || - ran.second != cur->appearance.subid) - { - const TerrainTile &tile = map->getTile(cur->visitablePos()); - cur->appearance = VLC->dobjinfo->pickCandidates(Obj(ran.first),ran.second, tile.terType).front(); - } - } - //we have to replace normal random object - cur->ID = ran.first; - cur->subID = ran.second; - map->removeBlockVisTiles(cur, true); //recalculate blockvis tiles - picked object might have different than random placeholder - map->addBlockVisTiles(cur); -} - -int CGameState::getDate(Date::EDateType mode) const -{ - int temp; - switch (mode) - { - case Date::DAY: - return day; - case Date::DAY_OF_WEEK: //day of week - temp = (day)%7; // 1 - Monday, 7 - Sunday - return temp ? temp : 7; - case Date::WEEK: //current week - temp = ((day-1)/7)+1; - if (!(temp%4)) - return 4; - else - return (temp%4); - case Date::MONTH: //current month - return ((day-1)/28)+1; - case Date::DAY_OF_MONTH: //day of month - temp = (day)%28; - if (temp) - return temp; - else return 28; - } - return 0; -} - -CGameState::CGameState() -{ - gs = this; - mx = new boost::shared_mutex(); - applierGs = new CApplier; - registerTypesClientPacks1(*applierGs); - registerTypesClientPacks2(*applierGs); - //objCaller = new CObjectCallersHandler; - globalEffects.setDescription("Global effects"); - globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS); -} - -CGameState::~CGameState() -{ - //delete mx;//TODO: crash on Linux (mutex must be unlocked before destruction) - map.dellNull(); - curB.dellNull(); - //delete scenarioOps; //TODO: fix for loading ind delete - //delete initialOpts; - delete applierGs; - //delete objCaller; - - for(auto ptr : hpool.heroesPool) // clean hero pool - ptr.second.dellNull(); -} - -BattleInfo * CGameState::setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance *town) -{ - const TerrainTile &t = map->getTile(tile); - ETerrainType terrain = t.terType; - if(t.isCoastal() && !t.isWater()) - terrain = ETerrainType::SAND; - - BFieldType terType = battleGetBattlefieldType(tile); - if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat) - terType = BFieldType::SHIP_TO_SHIP; - return BattleInfo::setupBattle(tile, terrain, terType, armies, heroes, creatureBank, town); -} - -void CGameState::init(StartInfo * si) -{ - logGlobal->infoStream() << "\tUsing random seed: "<< si->seedToBeUsed; - rand.setSeed(si->seedToBeUsed); - scenarioOps = CMemorySerializer::deepCopy(*si).release(); - initialOpts = CMemorySerializer::deepCopy(*si).release(); - si = nullptr; - - switch(scenarioOps->mode) - { - case StartInfo::NEW_GAME: - initNewGame(); - break; - case StartInfo::CAMPAIGN: - initCampaign(); - break; - case StartInfo::DUEL: - initDuel(); - return; - default: - logGlobal->errorStream() << "Wrong mode: " << (int)scenarioOps->mode; - return; - } - VLC->arth->initAllowedArtifactsList(map->allowedArtifact); - logGlobal->infoStream() << "Map loaded!"; - - checkMapChecksum(); - - day = 0; - - logGlobal->debugStream() << "Initialization:"; - - initPlayerStates(); - placeCampaignHeroes(); - initGrailPosition(); - initRandomFactionsForPlayers(); - randomizeMapObjects(); - placeStartingHeroes(); - initStartingResources(); - initHeroes(); - initStartingBonus(); - initTowns(); - initMapObjects(); - buildBonusSystemTree(); - initVisitingAndGarrisonedHeroes(); - initFogOfWar(); - - logGlobal->debugStream() << "\tChecking objectives"; - map->checkForObjectives(); //needs to be run when all objects are properly placed - - auto seedAfterInit = rand.nextInt(); - logGlobal->infoStream() << "Seed after init is " << seedAfterInit << " (before was " << scenarioOps->seedToBeUsed << ")"; - if(scenarioOps->seedPostInit > 0) - { - //RNG must be in the same state on all machines when initialization is done (otherwise we have desync) - assert(scenarioOps->seedPostInit == seedAfterInit); - } - else - { - scenarioOps->seedPostInit = seedAfterInit; //store the post init "seed" - } -} - -void CGameState::initNewGame() -{ - if(scenarioOps->createRandomMap()) - { - logGlobal->infoStream() << "Create random map."; - CStopWatch sw; - - // Gen map - CMapGenerator mapGenerator(scenarioOps->mapGenOptions, scenarioOps->seedToBeUsed); - map = mapGenerator.generate().release(); - - // Update starting options - for(int i = 0; i < map->players.size(); ++i) - { - const auto & playerInfo = map->players[i]; - if(playerInfo.canAnyonePlay()) - { - PlayerSettings & playerSettings = scenarioOps->playerInfos[PlayerColor(i)]; - playerSettings.compOnly = !playerInfo.canHumanPlay; - playerSettings.team = playerInfo.team; - playerSettings.castle = playerInfo.defaultCastle(); - if(playerSettings.playerID == PlayerSettings::PLAYER_AI && playerSettings.name.empty()) - { - playerSettings.name = VLC->generaltexth->allTexts[468]; - } - playerSettings.color = PlayerColor(i); - } - else - { - scenarioOps->playerInfos.erase(PlayerColor(i)); - } - } - - logGlobal->infoStream() << boost::format("Generated random map in %i ms.") % sw.getDiff(); - } - else - { - logGlobal->infoStream() << "Open map file: " << scenarioOps->mapname; - map = CMapService::loadMap(scenarioOps->mapname).release(); - } -} - -void CGameState::initCampaign() -{ - logGlobal->infoStream() << "Open campaign map file: " << scenarioOps->campState->currentMap; - auto campaign = scenarioOps->campState; - assert(vstd::contains(campaign->camp->mapPieces, *scenarioOps->campState->currentMap)); - - std::string scenarioName = scenarioOps->mapname.substr(0, scenarioOps->mapname.find('.')); - boost::to_lower(scenarioName); - scenarioName += ':' + boost::lexical_cast(*campaign->currentMap); - - std::string & mapContent = campaign->camp->mapPieces[*campaign->currentMap]; - auto buffer = reinterpret_cast(mapContent.data()); - map = CMapService::loadMap(buffer, mapContent.size(), scenarioName).release(); -} - -void CGameState::initDuel() -{ - DuelParameters dp; - try //CLoadFile likes throwing - { - if(boost::algorithm::ends_with(scenarioOps->mapname, ".json")) - { - logGlobal->infoStream() << "Loading duel settings from JSON file: " << scenarioOps->mapname; - dp = DuelParameters::fromJSON(scenarioOps->mapname); - logGlobal->infoStream() << "JSON file has been successfully read!"; - } - else - { - CLoadFile lf(scenarioOps->mapname); - lf >> dp; - } - } - catch(...) - { - logGlobal->errorStream() << "Cannot load duel settings from " << scenarioOps->mapname; - throw; - } - - const CArmedInstance *armies[2] = {nullptr}; - const CGHeroInstance *heroes[2] = {nullptr}; - CGTownInstance *town = nullptr; - - for(int i = 0; i < 2; i++) - { - CArmedInstance *obj = nullptr; - if(dp.sides[i].heroId >= 0) - { - const DuelParameters::SideSettings &ss = dp.sides[i]; - auto h = new CGHeroInstance(); - armies[i] = heroes[i] = h; - obj = h; - h->subID = ss.heroId; - for(int i = 0; i < ss.heroPrimSkills.size(); i++) - h->pushPrimSkill(static_cast(i), ss.heroPrimSkills[i]); - - if(!ss.spells.empty()) - { - h->putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0)); - boost::copy(ss.spells, std::inserter(h->spells, h->spells.begin())); - } - - for(auto &parka : ss.artifacts) - { - h->putArtifact(ArtifactPosition(parka.first), parka.second); - } - - typedef const std::pair &TSecSKill; - for(TSecSKill secSkill : ss.heroSecSkills) - h->setSecSkillLevel(SecondarySkill(secSkill.first), secSkill.second, 1); - - h->initHero(HeroTypeID(h->subID)); - obj->initObj(); - } - else - { - auto c = new CGCreature(); - armies[i] = obj = c; - //c->subID = 34; - } - - obj->setOwner(PlayerColor(i)); - - for(int j = 0; j < ARRAY_COUNT(dp.sides[i].stacks); j++) - { - CreatureID cre = dp.sides[i].stacks[j].type; - TQuantity count = dp.sides[i].stacks[j].count; - if(count || obj->hasStackAtSlot(SlotID(j))) - obj->setCreature(SlotID(j), cre, count); - } - - for(const DuelParameters::CusomCreature &cc : dp.creatures) - { - CCreature *c = VLC->creh->creatures[cc.id]; - if(cc.attack >= 0) - c->getBonusLocalFirst(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK))->val = cc.attack; - if(cc.defense >= 0) - c->getBonusLocalFirst(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE))->val = cc.defense; - if(cc.speed >= 0) - c->getBonusLocalFirst(Selector::type(Bonus::STACKS_SPEED))->val = cc.speed; - if(cc.HP >= 0) - c->getBonusLocalFirst(Selector::type(Bonus::STACK_HEALTH))->val = cc.HP; - if(cc.dmg >= 0) - { - c->getBonusLocalFirst(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1))->val = cc.dmg; - c->getBonusLocalFirst(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2))->val = cc.dmg; - } - if(cc.shoots >= 0) - c->getBonusLocalFirst(Selector::type(Bonus::SHOTS))->val = cc.shoots; - } - } - - curB = BattleInfo::setupBattle(int3(), dp.terType, dp.bfieldType, armies, heroes, false, town); - curB->obstacles = dp.obstacles; - curB->localInit(); -} - -void CGameState::checkMapChecksum() -{ - logGlobal->infoStream() << "\tOur checksum for the map: "<< map->checksum; - if(scenarioOps->mapfileChecksum) - { - logGlobal->infoStream() << "\tServer checksum for " << scenarioOps->mapname <<": "<< scenarioOps->mapfileChecksum; - if(map->checksum != scenarioOps->mapfileChecksum) - { - logGlobal->errorStream() << "Wrong map checksum!!!"; - throw std::runtime_error("Wrong checksum"); - } - } - else - { - scenarioOps->mapfileChecksum = map->checksum; - } -} - -void CGameState::initGrailPosition() -{ - logGlobal->debugStream() << "\tPicking grail position"; - //pick grail location - if(map->grailPos.x < 0 || map->grailRadious) //grail not set or set within a radius around some place - { - if(!map->grailRadious) //radius not given -> anywhere on map - map->grailRadious = map->width * 2; - - std::vector allowedPos; - static const int BORDER_WIDTH = 9; // grail must be at least 9 tiles away from border - - // add all not blocked tiles in range - for (int i = BORDER_WIDTH; i < map->width - BORDER_WIDTH ; i++) - { - for (int j = BORDER_WIDTH; j < map->height - BORDER_WIDTH; j++) - { - for (int k = 0; k < (map->twoLevel ? 2 : 1); k++) - { - const TerrainTile &t = map->getTile(int3(i, j, k)); - if(!t.blocked - && !t.visitable - && t.terType != ETerrainType::WATER - && t.terType != ETerrainType::ROCK - && map->grailPos.dist2dSQ(int3(i, j, k)) <= (map->grailRadious * map->grailRadious)) - allowedPos.push_back(int3(i,j,k)); - } - } - } - - //remove tiles with holes - for(auto & elem : map->objects) - if(elem && elem->ID == Obj::HOLE) - allowedPos -= elem->pos; - - if(!allowedPos.empty()) - { - map->grailPos = *RandomGeneratorUtil::nextItem(allowedPos, rand); - } - else - { - logGlobal->warnStream() << "Warning: Grail cannot be placed, no appropriate tile found!"; - } - } -} - -void CGameState::initRandomFactionsForPlayers() -{ - logGlobal->debugStream() << "\tPicking random factions for players"; - for(auto & elem : scenarioOps->playerInfos) - { - if(elem.second.castle==-1) - { - auto randomID = rand.nextInt(map->players[elem.first.getNum()].allowedFactions.size() - 1); - auto iter = map->players[elem.first.getNum()].allowedFactions.begin(); - std::advance(iter, randomID); - - elem.second.castle = *iter; - } - } -} - -void CGameState::randomizeMapObjects() -{ - logGlobal->debugStream() << "\tRandomizing objects"; - for(CGObjectInstance *obj : map->objects) - { - if(!obj) continue; - - randomizeObject(obj); - obj->hoverName = VLC->generaltexth->names[obj->ID]; - - //handle Favouring Winds - mark tiles under it - if(obj->ID == Obj::FAVORABLE_WINDS) - { - for (int i = 0; i < obj->getWidth() ; i++) - { - for (int j = 0; j < obj->getHeight() ; j++) - { - int3 pos = obj->pos - int3(i,j,0); - if(map->isInTheMap(pos)) map->getTile(pos).extTileFlags |= 128; - } - } - } - } -} - -void CGameState::initPlayerStates() -{ - logGlobal->debugStream() << "\tCreating player entries in gs"; - for(auto & elem : scenarioOps->playerInfos) - { - std::pair ins(elem.first,PlayerState()); - ins.second.color=ins.first; - ins.second.human = elem.second.playerID; - ins.second.team = map->players[ins.first.getNum()].team; - teams[ins.second.team].id = ins.second.team;//init team - teams[ins.second.team].players.insert(ins.first);//add player to team - players.insert(ins); - } -} - -void CGameState::placeCampaignHeroes() -{ - if (scenarioOps->campState) - { - // place bonus hero - auto campaignBonus = scenarioOps->campState->getBonusForCurrentMap(); - bool campaignGiveHero = campaignBonus && campaignBonus.get().type == CScenarioTravel::STravelBonus::HERO; - - if(campaignGiveHero) - { - auto playerColor = PlayerColor(campaignBonus->info1); - auto it = scenarioOps->playerInfos.find(playerColor); - if(it != scenarioOps->playerInfos.end()) - { - auto heroTypeId = campaignBonus->info2; - if(heroTypeId == 0xffff) // random bonus hero - { - heroTypeId = pickUnusedHeroTypeRandomly(playerColor); - } - - placeStartingHero(playerColor, HeroTypeID(heroTypeId), map->players[playerColor.getNum()].posOfMainTown); - } - } - - // replace heroes placeholders - auto crossoverHeroes = getCrossoverHeroesFromPreviousScenarios(); - - if(!crossoverHeroes.heroesFromAnyPreviousScenarios.empty()) - { - logGlobal->debugStream() << "\tGenerate list of hero placeholders"; - auto campaignHeroReplacements = generateCampaignHeroesToReplace(crossoverHeroes); - - logGlobal->debugStream() << "\tPrepare crossover heroes"; - prepareCrossoverHeroes(campaignHeroReplacements, scenarioOps->campState->getCurrentScenario().travelOptions); - - // remove same heroes on the map which will be added through crossover heroes - // INFO: we will remove heroes because later it may be possible that the API doesn't allow having heroes - // with the same hero type id - std::vector removedHeroes; - - for(auto & campaignHeroReplacement : campaignHeroReplacements) - { - auto hero = getUsedHero(HeroTypeID(campaignHeroReplacement.hero->subID)); - if(hero) - { - removedHeroes.push_back(hero); - map->heroesOnMap -= hero; - map->objects[hero->id.getNum()] = nullptr; - map->removeBlockVisTiles(hero, true); - } - } - - logGlobal->debugStream() << "\tReplace placeholders with heroes"; - replaceHeroesPlaceholders(campaignHeroReplacements); - - // remove hero placeholders on map - for(auto obj : map->objects) - { - if(obj && obj->ID == Obj::HERO_PLACEHOLDER) - { - auto heroPlaceholder = dynamic_cast(obj.get()); - map->removeBlockVisTiles(heroPlaceholder, true); - map->objects[heroPlaceholder->id.getNum()] = nullptr; - delete heroPlaceholder; - } - } - - // now add removed heroes again with unused type ID - for(auto hero : removedHeroes) - { - si32 heroTypeId = 0; - if(hero->ID == Obj::HERO) - { - heroTypeId = pickUnusedHeroTypeRandomly(hero->tempOwner); - } - else if(hero->ID == Obj::PRISON) - { - auto unusedHeroTypeIds = getUnusedAllowedHeroes(); - if(!unusedHeroTypeIds.empty()) - { - heroTypeId = (*RandomGeneratorUtil::nextItem(unusedHeroTypeIds, rand)).getNum(); - } - else - { - logGlobal->errorStream() << "No free hero type ID found to replace prison."; - assert(0); - } - } - else - { - assert(0); // should not happen - } - - hero->subID = heroTypeId; - hero->portrait = hero->subID; - map->getEditManager()->insertObject(hero, hero->pos); - } - } - } -} - -void CGameState::placeStartingHero(PlayerColor playerColor, HeroTypeID heroTypeId, int3 townPos) -{ - townPos.x += 1; - - CGHeroInstance * hero = static_cast(createObject(Obj::HERO, heroTypeId.getNum(), townPos, playerColor)); - map->getEditManager()->insertObject(hero, townPos); -} - -CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenarios() const -{ - CrossoverHeroesList crossoverHeroes; - - auto campaignState = scenarioOps->campState; - auto bonus = campaignState->getBonusForCurrentMap(); - if (bonus && bonus->type == CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO) - { - crossoverHeroes.heroesFromAnyPreviousScenarios = crossoverHeroes.heroesFromPreviousScenario = campaignState->camp->scenarios[bonus->info2].crossoverHeroes; - } - else - { - if(!campaignState->mapsConquered.empty()) - { - crossoverHeroes.heroesFromPreviousScenario = campaignState->camp->scenarios[campaignState->mapsConquered.back()].crossoverHeroes; - - for(auto mapNr : campaignState->mapsConquered) - { - // create a list of deleted heroes - auto & scenario = campaignState->camp->scenarios[mapNr]; - auto lostCrossoverHeroes = scenario.getLostCrossoverHeroes(); - - // remove heroes which didn't reached the end of the scenario, but were available at the start - for(auto hero : lostCrossoverHeroes) - { - crossoverHeroes.heroesFromAnyPreviousScenarios.erase(range::remove_if(crossoverHeroes.heroesFromAnyPreviousScenarios, - CGObjectInstanceBySubIdFinder(hero)), crossoverHeroes.heroesFromAnyPreviousScenarios.end()); - } - - // now add heroes which completed the scenario - for(auto hero : scenario.crossoverHeroes) - { - // add new heroes and replace old heroes with newer ones - auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, CGObjectInstanceBySubIdFinder(hero)); - if (it != crossoverHeroes.heroesFromAnyPreviousScenarios.end()) - { - // replace old hero with newer one - crossoverHeroes.heroesFromAnyPreviousScenarios[it - crossoverHeroes.heroesFromAnyPreviousScenarios.begin()] = hero; - } - else - { - // add new hero - crossoverHeroes.heroesFromAnyPreviousScenarios.push_back(hero); - } - } - } - } - } - - return crossoverHeroes; -} - -void CGameState::prepareCrossoverHeroes(std::vector & campaignHeroReplacements, const CScenarioTravel & travelOptions) const -{ - // create heroes list for convenience iterating - std::vector crossoverHeroes; - for(auto & campaignHeroReplacement : campaignHeroReplacements) - { - crossoverHeroes.push_back(campaignHeroReplacement.hero); - } - - // TODO replace magic numbers with named constants - // TODO this logic (what should be kept) should be part of CScenarioTravel and be exposed via some clean set of methods - if(!(travelOptions.whatHeroKeeps & 1)) - { - //trimming experience - for(CGHeroInstance * cgh : crossoverHeroes) - { - cgh->initExp(); - } - } - - if(!(travelOptions.whatHeroKeeps & 2)) - { - //trimming prim skills - for(CGHeroInstance * cgh : crossoverHeroes) - { - for(int g=0; ggetBonusLocalFirst(sel)->val = cgh->type->heroClass->primarySkillInitial[g]; - } - } - } - - if(!(travelOptions.whatHeroKeeps & 4)) - { - //trimming sec skills - for(CGHeroInstance * cgh : crossoverHeroes) - { - cgh->secSkills = cgh->type->secSkillsInit; - cgh->recreateSecondarySkillsBonuses(); - } - } - - if(!(travelOptions.whatHeroKeeps & 8)) - { - for(CGHeroInstance * cgh : crossoverHeroes) - { - // Trimming spells - cgh->spells.clear(); - - // Spellbook will also be removed - if (cgh->hasSpellbook()) - ArtifactLocation(cgh, ArtifactPosition(ArtifactPosition::SPELLBOOK)).removeArtifact(); - } - } - - if(!(travelOptions.whatHeroKeeps & 16)) - { - //trimming artifacts - for(CGHeroInstance * hero : crossoverHeroes) - { - size_t totalArts = GameConstants::BACKPACK_START + hero->artifactsInBackpack.size(); - for (size_t i = 0; i < totalArts; i++ ) - { - auto artifactPosition = ArtifactPosition(i); - if(artifactPosition == ArtifactPosition::SPELLBOOK) continue; // do not handle spellbook this way - - const ArtSlotInfo *info = hero->getSlot(artifactPosition); - if(!info) - continue; - - // TODO: why would there be nullptr artifacts? - const CArtifactInstance *art = info->artifact; - if(!art) - continue; - - int id = art->artType->id; - assert( 8*18 > id );//number of arts that fits into h3m format - bool takeable = travelOptions.artifsKeptByHero[id / 8] & ( 1 << (id%8) ); - - ArtifactLocation al(hero, artifactPosition); - if(!takeable && !al.getSlot()->locked) //don't try removing locked artifacts -> it crashes #1719 - al.removeArtifact(); - } - } - } - - //trimming creatures - for(CGHeroInstance * cgh : crossoverHeroes) - { - auto shouldSlotBeErased = [&](const std::pair & j) -> bool - { - CreatureID::ECreatureID crid = j.second->getCreatureID().toEnum(); - return !(travelOptions.monstersKeptByHero[crid / 8] & (1 << (crid % 8))); - }; - - auto stacksCopy = cgh->stacks; //copy of the map, so we can iterate iover it and remove stacks - for(auto &slotPair : stacksCopy) - if(shouldSlotBeErased(slotPair)) - cgh->eraseStack(slotPair.first); - } - - // Removing short-term bonuses - for(CGHeroInstance * cgh : crossoverHeroes) - { - cgh->popBonuses(Selector::durationType(Bonus::ONE_DAY)); - cgh->popBonuses(Selector::durationType(Bonus::ONE_WEEK)); - cgh->popBonuses(Selector::durationType(Bonus::N_TURNS)); - cgh->popBonuses(Selector::durationType(Bonus::N_DAYS)); - cgh->popBonuses(Selector::durationType(Bonus::ONE_BATTLE)); - } - -} - -void CGameState::placeStartingHeroes() -{ - logGlobal->debugStream() << "\tGiving starting hero"; - - for(auto & playerSettingPair : scenarioOps->playerInfos) - { - auto playerColor = playerSettingPair.first; - auto & playerInfo = map->players[playerColor.getNum()]; - if(playerInfo.generateHeroAtMainTown && playerInfo.hasMainTown) - { - // Do not place a starting hero if the hero was already placed due to a campaign bonus - if(scenarioOps->campState) - { - if(auto campaignBonus = scenarioOps->campState->getBonusForCurrentMap()) - { - if(campaignBonus->type == CScenarioTravel::STravelBonus::HERO && playerColor == PlayerColor(campaignBonus->info1)) continue; - } - } - - int heroTypeId = pickNextHeroType(playerColor); - if(playerSettingPair.second.hero == -1) playerSettingPair.second.hero = heroTypeId; - - placeStartingHero(playerColor, HeroTypeID(heroTypeId), playerInfo.posOfMainTown); - } - } -} - -void CGameState::initStartingResources() -{ - logGlobal->debugStream() << "\tSetting up resources"; - const JsonNode config(ResourceID("config/startres.json")); - const JsonVector &vector = config["difficulty"].Vector(); - const JsonNode &level = vector[scenarioOps->difficulty]; - - TResources startresAI(level["ai"]); - TResources startresHuman(level["human"]); - - for (auto & elem : players) - { - PlayerState &p = elem.second; - - if (p.human) - p.resources = startresHuman; - else - p.resources = startresAI; - } - - auto getHumanPlayerInfo = [&]() -> std::vector - { - std::vector ret; - for(auto it = scenarioOps->playerInfos.cbegin(); - it != scenarioOps->playerInfos.cend(); ++it) - { - if(it->second.playerID != PlayerSettings::PLAYER_AI) - ret.push_back(&it->second); - } - - return ret; - }; - - //give start resource bonus in case of campaign - if (scenarioOps->mode == StartInfo::CAMPAIGN) - { - auto chosenBonus = scenarioOps->campState->getBonusForCurrentMap(); - if(chosenBonus && chosenBonus->type == CScenarioTravel::STravelBonus::RESOURCE) - { - std::vector people = getHumanPlayerInfo(); //players we will give resource bonus - for(const PlayerSettings *ps : people) - { - std::vector res; //resources we will give - switch (chosenBonus->info1) - { - case 0: case 1: case 2: case 3: case 4: case 5: case 6: - res.push_back(chosenBonus->info1); - break; - case 0xFD: //wood+ore - res.push_back(Res::WOOD); res.push_back(Res::ORE); - break; - case 0xFE: //rare - res.push_back(Res::MERCURY); res.push_back(Res::SULFUR); res.push_back(Res::CRYSTAL); res.push_back(Res::GEMS); - break; - default: - assert(0); - break; - } - //increasing resource quantity - for (auto & re : res) - { - players[ps->color].resources[re] += chosenBonus->info2; - } - } - } - } -} - -void CGameState::initHeroes() -{ - for(auto hero : map->heroesOnMap) //heroes instances initialization - { - if (hero->getOwner() == PlayerColor::UNFLAGGABLE) - { - logGlobal->warnStream() << "Warning - hero with uninitialized owner!"; - continue; - } - - hero->initHero(); - getPlayer(hero->getOwner())->heroes.push_back(hero); - map->allHeroes[hero->type->ID.getNum()] = hero; - } - - for(auto obj : map->objects) //prisons - { - if(obj && obj->ID == Obj::PRISON) - map->allHeroes[obj->subID] = dynamic_cast(obj.get()); - } - - std::set heroesToCreate = getUnusedAllowedHeroes(); //ids of heroes to be created and put into the pool - for(auto ph : map->predefinedHeroes) - { - if(!vstd::contains(heroesToCreate, HeroTypeID(ph->subID))) - continue; - ph->initHero(); - hpool.heroesPool[ph->subID] = ph; - hpool.pavailable[ph->subID] = 0xff; - heroesToCreate.erase(ph->type->ID); - - map->allHeroes[ph->subID] = ph; - } - - for(HeroTypeID htype : heroesToCreate) //all not used allowed heroes go with default state into the pool - { - auto vhi = new CGHeroInstance(); - vhi->initHero(htype); - - int typeID = htype.getNum(); - map->allHeroes[typeID] = vhi; - hpool.heroesPool[typeID] = vhi; - hpool.pavailable[typeID] = 0xff; - } - - for(auto & elem : map->disposedHeroes) - { - hpool.pavailable[elem.heroId] = elem.players; - } - - if (scenarioOps->mode == StartInfo::CAMPAIGN) //give campaign bonuses for specific / best hero - { - auto chosenBonus = scenarioOps->campState->getBonusForCurrentMap(); - if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->info1 != 0xFFFE) //exclude generated heroes - { - //find human player - PlayerColor humanPlayer=PlayerColor::NEUTRAL; - for (auto & elem : players) - { - if(elem.second.human) - { - humanPlayer = elem.first; - break; - } - } - assert(humanPlayer != PlayerColor::NEUTRAL); - - std::vector > & heroes = players[humanPlayer].heroes; - - if (chosenBonus->info1 == 0xFFFD) //most powerful - { - int maxB = -1; - for (int b=0; bgetTotalStrength() > heroes[maxB]->getTotalStrength()) - { - maxB = b; - } - } - if(maxB < 0) - logGlobal->warnStream() << "Warning - cannot give bonus to hero cause there are no heroes!"; - else - giveCampaignBonusToHero(heroes[maxB]); - } - else //specific hero - { - for (auto & heroe : heroes) - { - if (heroe->subID == chosenBonus->info1) - { - giveCampaignBonusToHero(heroe); - break; - } - } - } - } - } -} - -void CGameState::giveCampaignBonusToHero(CGHeroInstance * hero) -{ - const boost::optional & curBonus = scenarioOps->campState->getBonusForCurrentMap(); - if(!curBonus) - return; - - if(curBonus->isBonusForHero()) - { - //apply bonus - switch (curBonus->type) - { - case CScenarioTravel::STravelBonus::SPELL: - hero->spells.insert(SpellID(curBonus->info2)); - break; - case CScenarioTravel::STravelBonus::MONSTER: - { - for(int i=0; islotEmpty(SlotID(i))) - { - hero->addToSlot(SlotID(i), CreatureID(curBonus->info2), curBonus->info3); - break; - } - } - } - break; - case CScenarioTravel::STravelBonus::ARTIFACT: - gs->giveHeroArtifact(hero, static_cast(curBonus->info2)); - break; - case CScenarioTravel::STravelBonus::SPELL_SCROLL: - { - CArtifactInstance * scroll = CArtifactInstance::createScroll(SpellID(curBonus->info2).toSpell()); - scroll->putAt(ArtifactLocation(hero, scroll->firstAvailableSlot(hero))); - } - break; - case CScenarioTravel::STravelBonus::PRIMARY_SKILL: - { - const ui8* ptr = reinterpret_cast(&curBonus->info2); - for (int g=0; gcampState->currentMap, g); - hero->addNewBonus(bb); - } - } - break; - case CScenarioTravel::STravelBonus::SECONDARY_SKILL: - hero->setSecSkillLevel(SecondarySkill(curBonus->info2), curBonus->info3, true); - break; - } - } -} - -void CGameState::initFogOfWar() -{ - logGlobal->debugStream() << "\tFog of war"; //FIXME: should be initialized after all bonuses are set - for(auto & elem : teams) - { - elem.second.fogOfWarMap.resize(map->width); - for(int g=0; gwidth; ++g) - elem.second.fogOfWarMap[g].resize(map->height); - - for(int g=-0; gwidth; ++g) - for(int h=0; hheight; ++h) - elem.second.fogOfWarMap[g][h].resize(map->twoLevel ? 2 : 1, 0); - - for(int g=0; gwidth; ++g) - for(int h=0; hheight; ++h) - for(int v = 0; v < (map->twoLevel ? 2 : 1); ++v) - elem.second.fogOfWarMap[g][h][v] = 0; - - for(CGObjectInstance *obj : map->objects) - { - if(!obj || !vstd::contains(elem.second.players, obj->tempOwner)) continue; //not a flagged object - - std::unordered_set tiles; - obj->getSightTiles(tiles); - for(int3 tile : tiles) - { - elem.second.fogOfWarMap[tile.x][tile.y][tile.z] = 1; - } - } - } -} - -void CGameState::initStartingBonus() -{ - logGlobal->debugStream() << "\tStarting bonuses"; - for(auto & elem : players) - { - //starting bonus - if(scenarioOps->playerInfos[elem.first].bonus==PlayerSettings::RANDOM) - scenarioOps->playerInfos[elem.first].bonus = static_cast(rand.nextInt(2)); - switch(scenarioOps->playerInfos[elem.first].bonus) - { - case PlayerSettings::GOLD: - elem.second.resources[Res::GOLD] += rand.nextInt(500, 1000); - break; - case PlayerSettings::RESOURCE: - { - int res = VLC->townh->factions[scenarioOps->playerInfos[elem.first].castle]->town->primaryRes; - if(res == Res::WOOD_AND_ORE) - { - elem.second.resources[Res::WOOD] += rand.nextInt(5, 10); - elem.second.resources[Res::ORE] += rand.nextInt(5, 10); - } - else - { - elem.second.resources[res] += rand.nextInt(3, 6); - } - break; - } - case PlayerSettings::ARTIFACT: - { - if(!elem.second.heroes.size()) - { - logGlobal->debugStream() << "Cannot give starting artifact - no heroes!"; - break; - } - CArtifact *toGive; - toGive = VLC->arth->artifacts[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE)]; - - CGHeroInstance *hero = elem.second.heroes[0]; - giveHeroArtifact(hero, toGive->id); - } - break; - } - } -} - -void CGameState::initTowns() -{ - logGlobal->debugStream() << "\tTowns"; - - //campaign bonuses for towns - if (scenarioOps->mode == StartInfo::CAMPAIGN) - { - auto chosenBonus = scenarioOps->campState->getBonusForCurrentMap(); - - if (chosenBonus && chosenBonus->type == CScenarioTravel::STravelBonus::BUILDING) - { - for (int g=0; gtowns.size(); ++g) - { - PlayerState * owner = getPlayer(map->towns[g]->getOwner()); - if (owner) - { - PlayerInfo & pi = map->players[owner->color.getNum()]; - - if (owner->human && //human-owned - map->towns[g]->pos == pi.posOfMainTown + int3(2, 0, 0)) - { - map->towns[g]->builtBuildings.insert( - CBuildingHandler::campToERMU(chosenBonus->info1, map->towns[g]->subID, map->towns[g]->builtBuildings)); - break; - } - } - } - } - } - - CGTownInstance::universitySkills.clear(); - for ( int i=0; i<4; i++) - CGTownInstance::universitySkills.push_back(14+i);//skills for university - - for (auto & elem : map->towns) - { - CGTownInstance * vti =(elem); - if(!vti->town) - { - vti->town = VLC->townh->factions[vti->subID]->town; - } - if(vti->name.empty()) - { - vti->name = *RandomGeneratorUtil::nextItem(vti->town->names, rand); - } - - //init buildings - if(vstd::contains(vti->builtBuildings, BuildingID::DEFAULT)) //give standard set of buildings - { - vti->builtBuildings.erase(BuildingID::DEFAULT); - vti->builtBuildings.insert(BuildingID::VILLAGE_HALL); - vti->builtBuildings.insert(BuildingID::TAVERN); - vti->builtBuildings.insert(BuildingID::DWELL_FIRST); - if(rand.nextInt(1) == 1) - { - vti->builtBuildings.insert(BuildingID::DWELL_LVL_2); - } - } - - //#1444 - remove entries that don't have buildings defined (like some unused extra town hall buildings) - vstd::erase_if(vti->builtBuildings, [vti](BuildingID bid){ - return !vti->town->buildings.count(bid) || !vti->town->buildings.at(bid); }); - - if (vstd::contains(vti->builtBuildings, BuildingID::SHIPYARD) && vti->shipyardStatus()==IBoatGenerator::TILE_BLOCKED) - vti->builtBuildings.erase(BuildingID::SHIPYARD);//if we have harbor without water - erase it (this is H3 behaviour) - - //init hordes - for (int i = 0; ibuiltBuildings,(-31-i))) //if we have horde for this level - { - vti->builtBuildings.erase(BuildingID(-31-i));//remove old ID - if (vti->town->hordeLvl.at(0) == i)//if town first horde is this one - { - vti->builtBuildings.insert(BuildingID::HORDE_1);//add it - if (vstd::contains(vti->builtBuildings,(BuildingID::DWELL_UP_FIRST+i)))//if we have upgraded dwelling as well - vti->builtBuildings.insert(BuildingID::HORDE_1_UPGR);//add it as well - } - if (vti->town->hordeLvl.at(1) == i)//if town second horde is this one - { - vti->builtBuildings.insert(BuildingID::HORDE_2); - if (vstd::contains(vti->builtBuildings,(BuildingID::DWELL_UP_FIRST+i))) - vti->builtBuildings.insert(BuildingID::HORDE_2_UPGR); - } - } - - //Early check for #1444-like problems - for(auto building : vti->builtBuildings) - { - assert(vti->town->buildings.at(building) != nullptr); - UNUSED(building); - } - - //town events - for(CCastleEvent &ev : vti->events) - { - for (int i = 0; itown->hordeLvl.at(0) == i) - ev.buildings.insert(BuildingID::HORDE_1); - if (vti->town->hordeLvl.at(1) == i) - ev.buildings.insert(BuildingID::HORDE_2); - } - } - //init spells - logGlobal->debugStream() << "\t\tTown init spells"; - vti->spells.resize(GameConstants::SPELL_LEVELS); - - for(ui32 z=0; zobligatorySpells.size();z++) - { - CSpell *s = vti->obligatorySpells[z].toSpell(); - vti->spells[s->level-1].push_back(s->id); - vti->possibleSpells -= s->id; - } - logGlobal->debugStream() << "\t\tTown init spells2"; - while(vti->possibleSpells.size()) - { - ui32 total=0; - int sel = -1; - - for(ui32 ps=0;pspossibleSpells.size();ps++) - total += vti->possibleSpells[ps].toSpell()->getProbability(vti->subID); - - if (total == 0) // remaining spells have 0 probability - break; - - auto r = rand.nextInt(total - 1); - for(ui32 ps=0; pspossibleSpells.size();ps++) - { - r -= vti->possibleSpells[ps].toSpell()->getProbability(vti->subID); - if(r<0) - { - sel = ps; - break; - } - } - if(sel<0) - sel=0; - - CSpell *s = vti->possibleSpells[sel].toSpell(); - vti->spells[s->level-1].push_back(s->id); - vti->possibleSpells -= s->id; - } - vti->possibleSpells.clear(); - if(vti->getOwner() != PlayerColor::NEUTRAL) - getPlayer(vti->getOwner())->towns.push_back(vti); - logGlobal->debugStream() << "\t\tTown init spells3"; - - } -} - -void CGameState::initMapObjects() -{ - logGlobal->debugStream() << "\tObject initialization"; -// objCaller->preInit(); - for(CGObjectInstance *obj : map->objects) - { - if(obj) - obj->initObj(); - } - for(CGObjectInstance *obj : map->objects) - { - if(!obj) - continue; - - switch (obj->ID) - { - case Obj::QUEST_GUARD: - case Obj::SEER_HUT: - { - auto q = static_cast(obj); - assert (q); - q->setObjToKill(); - } - } - } - CGTeleport::postInit(); //pairing subterranean gates - - map->calculateGuardingGreaturePositions(); //calculate once again when all the guards are placed and initialized -} - -void CGameState::initVisitingAndGarrisonedHeroes() -{ - for(auto k=players.begin(); k!=players.end(); ++k) - { - if(k->first==PlayerColor::NEUTRAL) - continue; - - //init visiting and garrisoned heroes - for(CGHeroInstance *h : k->second.heroes) - { - for(CGTownInstance *t : k->second.towns) - { - int3 vistile = t->pos; vistile.x--; //tile next to the entrance - if(vistile == h->pos || h->pos==t->pos) - { - t->setVisitingHero(h); - if(h->pos == t->pos) //visiting hero placed in the editor has same pos as the town - we need to correct it - { - map->removeBlockVisTiles(h); - h->pos.x -= 1; - map->addBlockVisTiles(h); - } - break; - } - } - } - } -} - -BFieldType CGameState::battleGetBattlefieldType(int3 tile) -{ - if(tile==int3() && curB) - tile = curB->tile; - else if(tile==int3() && !curB) - return BFieldType::NONE; - - const TerrainTile &t = map->getTile(tile); - //fight in mine -> subterranean - if(dynamic_cast(t.visitableObjects.front())) - return BFieldType::SUBTERRANEAN; - - for(auto &obj : map->objects) - { - //look only for objects covering given tile - if( !obj || obj->pos.z != tile.z || !obj->coveringAt(tile.x, tile.y)) - continue; - - switch(obj->ID) - { - case Obj::CLOVER_FIELD: - return BFieldType::CLOVER_FIELD; - case Obj::CURSED_GROUND1: case Obj::CURSED_GROUND2: - return BFieldType::CURSED_GROUND; - case Obj::EVIL_FOG: - return BFieldType::EVIL_FOG; - case Obj::FAVORABLE_WINDS: - return BFieldType::FAVOURABLE_WINDS; - case Obj::FIERY_FIELDS: - return BFieldType::FIERY_FIELDS; - case Obj::HOLY_GROUNDS: - return BFieldType::HOLY_GROUND; - case Obj::LUCID_POOLS: - return BFieldType::LUCID_POOLS; - case Obj::MAGIC_CLOUDS: - return BFieldType::MAGIC_CLOUDS; - case Obj::MAGIC_PLAINS1: case Obj::MAGIC_PLAINS2: - return BFieldType::MAGIC_PLAINS; - case Obj::ROCKLANDS: - return BFieldType::ROCKLANDS; - } - } - - if(!t.isWater() && t.isCoastal()) - return BFieldType::SAND_SHORE; - - switch(t.terType) - { - case ETerrainType::DIRT: - return BFieldType(rand.nextInt(3, 5)); - case ETerrainType::SAND: - return BFieldType::SAND_MESAS; //TODO: coast support - case ETerrainType::GRASS: - return BFieldType(rand.nextInt(6, 7)); - case ETerrainType::SNOW: - return BFieldType(rand.nextInt(10, 11)); - case ETerrainType::SWAMP: - return BFieldType::SWAMP_TREES; - case ETerrainType::ROUGH: - return BFieldType::ROUGH; - case ETerrainType::SUBTERRANEAN: - return BFieldType::SUBTERRANEAN; - case ETerrainType::LAVA: - return BFieldType::LAVA; - case ETerrainType::WATER: - return BFieldType::SHIP; - case ETerrainType::ROCK: - return BFieldType::ROCKLANDS; - default: - return BFieldType::NONE; - } -} - -UpgradeInfo CGameState::getUpgradeInfo(const CStackInstance &stack) -{ - UpgradeInfo ret; - const CCreature *base = stack.type; - - const CGHeroInstance *h = stack.armyObj->ID == Obj::HERO ? static_cast(stack.armyObj) : nullptr; - const CGTownInstance *t = nullptr; - - if(stack.armyObj->ID == Obj::TOWN) - t = static_cast(stack.armyObj); - else if(h) - { //hero specialty - TBonusListPtr lista = h->getBonuses(Selector::typeSubtype(Bonus::SPECIAL_UPGRADE, base->idNumber)); - for(const Bonus *it : *lista) - { - auto nid = CreatureID(it->additionalInfo); - if (nid != base->idNumber) //in very specific case the upgrade is available by default (?) - { - ret.newID.push_back(nid); - ret.cost.push_back(VLC->creh->creatures[nid]->cost - base->cost); - } - } - t = h->visitedTown; - } - if(t) - { - for(const CGTownInstance::TCreaturesSet::value_type & dwelling : t->creatures) - { - if (vstd::contains(dwelling.second, base->idNumber)) //Dwelling with our creature - { - for(auto upgrID : dwelling.second) - { - if(vstd::contains(base->upgrades, upgrID)) //possible upgrade - { - ret.newID.push_back(upgrID); - ret.cost.push_back(VLC->creh->creatures[upgrID]->cost - base->cost); - } - } - } - } - } - - //hero is visiting Hill Fort - if(h && map->getTile(h->visitablePos()).visitableObjects.front()->ID == Obj::HILL_FORT) - { - static const int costModifiers[] = {0, 25, 50, 75, 100}; //we get cheaper upgrades depending on level - const int costModifier = costModifiers[std::min(std::max((int)base->level - 1, 0), ARRAY_COUNT(costModifiers) - 1)]; - - for(auto nid : base->upgrades) - { - ret.newID.push_back(nid); - ret.cost.push_back((VLC->creh->creatures[nid]->cost - base->cost) * costModifier / 100); - } - } - - if(ret.newID.size()) - ret.oldID = base->idNumber; - - for (Res::ResourceSet &cost : ret.cost) - cost.positive(); //upgrade cost can't be negative, ignore missing resources - - return ret; -} - -PlayerRelations::PlayerRelations CGameState::getPlayerRelations( PlayerColor color1, PlayerColor color2 ) -{ - if ( color1 == color2 ) - return PlayerRelations::SAME_PLAYER; - if(color1 == PlayerColor::NEUTRAL || color2 == PlayerColor::NEUTRAL) //neutral player has no friends - return PlayerRelations::ENEMIES; - - const TeamState * ts = getPlayerTeam(color1); - if (ts && vstd::contains(ts->players, color2)) - return PlayerRelations::ALLIES; - return PlayerRelations::ENEMIES; -} - -void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vector &vec, const boost::logic::tribool &onLand, bool limitCoastSailing) -{ - static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), - int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) }; - - //vec.reserve(8); //optimization - for (auto & dir : dirs) - { - const int3 hlp = tile + dir; - if(!map->isInTheMap(hlp)) - continue; - - const TerrainTile &hlpt = map->getTile(hlp); - -// //we cannot visit things from blocked tiles -// if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE) -// { -// continue; -// } - - if(srct.terType == ETerrainType::WATER && limitCoastSailing && hlpt.terType == ETerrainType::WATER && dir.x && dir.y) //diagonal move through water - { - int3 hlp1 = tile, - hlp2 = tile; - hlp1.x += dir.x; - hlp2.y += dir.y; - - if(map->getTile(hlp1).terType != ETerrainType::WATER || map->getTile(hlp2).terType != ETerrainType::WATER) - continue; - } - - if((indeterminate(onLand) || onLand == (hlpt.terType!=ETerrainType::WATER) ) - && hlpt.terType != ETerrainType::ROCK) - { - vec.push_back(hlp); - } - } -} - -int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, bool flying, int remainingMovePoints, bool checkLast) -{ - if(src == dest) //same tile - return 0; - - TerrainTile &s = map->getTile(src), - &d = map->getTile(dest); - - //get basic cost - int ret = h->getTileCost(d,s); - - if(d.blocked && flying) - { - bool freeFlying = h->getBonusesCount(Selector::typeSubtype(Bonus::FLYING_MOVEMENT, 1)) > 0; - - if(!freeFlying) - { - ret *= 1.4; //40% penalty for movement over blocked tile - } - } - else if (d.terType == ETerrainType::WATER) - { - if(h->boat && s.hasFavourableWinds() && d.hasFavourableWinds()) //Favourable Winds - ret *= 0.666; - else if (!h->boat && h->getBonusesCount(Selector::typeSubtype(Bonus::WATER_WALKING, 1)) > 0) - ret *= 1.4; //40% penalty for water walking - } - - if(src.x != dest.x && src.y != dest.y) //it's diagonal move - { - int old = ret; - ret *= 1.414213; - //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points - if(ret > remainingMovePoints && remainingMovePoints >= old) - { - return remainingMovePoints; - } - } - - - int left = remainingMovePoints-ret; - if(checkLast && left > 0 && remainingMovePoints-ret < 250) //it might be the last tile - if no further move possible we take all move points - { - std::vector vec; - vec.reserve(8); //optimization - getNeighbours(d, dest, vec, s.terType != ETerrainType::WATER, true); - for(auto & elem : vec) - { - int fcost = getMovementCost(h,dest, elem, flying, left, false); - if(fcost <= left) - { - return ret; - } - } - ret = remainingMovePoints; - } - return ret; -} - -void CGameState::apply(CPack *pack) -{ - ui16 typ = typeList.getTypeID(pack); - applierGs->apps[typ]->applyOnGS(this,pack); -} - -void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src, int movement) -{ - CPathfinder pathfinder(out, this, hero); - pathfinder.calculatePaths(src, movement); -} - -/** - * Tells if the tile is guarded by a monster as well as the position - * of the monster that will attack on it. - * - * @return int3(-1, -1, -1) if the tile is unguarded, or the position of - * the monster guarding the tile. - */ -std::vector CGameState::guardingCreatures (int3 pos) const -{ - std::vector guards; - const int3 originalPos = pos; - if (!map->isInTheMap(pos)) - return guards; - - const TerrainTile &posTile = map->getTile(pos); - if (posTile.visitable) - { - for (CGObjectInstance* obj : posTile.visitableObjects) - { - if(obj->blockVisit) - { - if (obj->ID == Obj::MONSTER) // Monster - guards.push_back(obj); - } - } - } - pos -= int3(1, 1, 0); // Start with top left. - for (int dx = 0; dx < 3; dx++) - { - for (int dy = 0; dy < 3; dy++) - { - if (map->isInTheMap(pos)) - { - const auto & tile = map->getTile(pos); - if (tile.visitable && (tile.isWater() == posTile.isWater())) - { - for (CGObjectInstance* obj : tile.visitableObjects) - { - if (obj->ID == Obj::MONSTER && map->checkForVisitableDir(pos, &map->getTile(originalPos), originalPos)) // Monster being able to attack investigated tile - { - guards.push_back(obj); - } - } - } - } - - pos.y++; - } - pos.y -= 3; - pos.x++; - } - return guards; - -} - -int3 CGameState::guardingCreaturePosition (int3 pos) const -{ - return gs->map->guardingCreaturePositions[pos.x][pos.y][pos.z]; -} - -bool CGameState::isVisible(int3 pos, PlayerColor player) -{ - if(player == PlayerColor::NEUTRAL) - return false; - return getPlayerTeam(player)->fogOfWarMap[pos.x][pos.y][pos.z]; -} - -bool CGameState::isVisible( const CGObjectInstance *obj, boost::optional player ) -{ - if(!player) - return true; - - if(*player == PlayerColor::NEUTRAL) //-> TODO ??? needed? - return false; - //object is visible when at least one blocked tile is visible - for(int fy=0; fy < obj->getHeight(); ++fy) - { - for(int fx=0; fx < obj->getWidth(); ++fx) - { - int3 pos = obj->pos + int3(-fx, -fy, 0); - - if ( map->isInTheMap(pos) && - obj->coveringAt(pos.x, pos.y) && - isVisible(pos, *player)) - return true; - } - } - return false; -} - -bool CGameState::checkForVisitableDir(const int3 & src, const int3 & dst) const -{ - const TerrainTile * pom = &map->getTile(dst); - return map->checkForVisitableDir(src, pom, dst); -} - -EVictoryLossCheckResult CGameState::checkForVictoryAndLoss(PlayerColor player) const -{ - const std::string & messageWonSelf = VLC->generaltexth->allTexts[659]; - const std::string & messageWonOther = VLC->generaltexth->allTexts[5]; - const std::string & messageLostSelf = VLC->generaltexth->allTexts[7]; - const std::string & messageLostOther = VLC->generaltexth->allTexts[8]; - - auto evaluateEvent = [=](const EventCondition & condition) - { - return this->checkForVictory(player, condition); - }; - - const PlayerState *p = CGameInfoCallback::getPlayer(player); - - //cheater or tester, but has entered the code... - if (p->enteredWinningCheatCode) - return EVictoryLossCheckResult::victory(messageWonSelf, messageWonOther); - - if (p->enteredLosingCheatCode) - return EVictoryLossCheckResult::defeat(messageLostSelf, messageLostOther); - - for (const TriggeredEvent & event : map->triggeredEvents) - { - if ((event.trigger.test(evaluateEvent))) - { - if (event.effect.type == EventEffect::VICTORY) - return EVictoryLossCheckResult::victory(event.onFulfill, event.effect.toOtherMessage); - - if (event.effect.type == EventEffect::DEFEAT) - return EVictoryLossCheckResult::defeat(event.onFulfill, event.effect.toOtherMessage); - } - } - - if (checkForStandardLoss(player)) - { - return EVictoryLossCheckResult::defeat(messageLostSelf, messageLostOther); - } - return EVictoryLossCheckResult(); -} - -bool CGameState::checkForVictory( PlayerColor player, const EventCondition & condition ) const -{ - const PlayerState *p = CGameInfoCallback::getPlayer(player); - switch (condition.condition) - { - case EventCondition::STANDARD_WIN: - { - return player == checkForStandardWin(); - } - case EventCondition::HAVE_ARTIFACT: //check if any hero has winning artifact - { - for(auto & elem : p->heroes) - if(elem->hasArt(condition.objectType)) - return true; - return false; - } - case EventCondition::HAVE_CREATURES: - { - //check if in players armies there is enough creatures - int total = 0; //creature counter - for(size_t i = 0; i < map->objects.size(); i++) - { - const CArmedInstance *ai = nullptr; - if(map->objects[i] - && map->objects[i]->tempOwner == player //object controlled by player - && (ai = dynamic_cast(map->objects[i].get()))) //contains army - { - for(auto & elem : ai->Slots()) //iterate through army - if(elem.second->type->idNumber == condition.objectType) //it's searched creature - total += elem.second->count; - } - } - return total >= condition.value; - } - case EventCondition::HAVE_RESOURCES: - { - return p->resources[condition.objectType] >= condition.value; - } - case EventCondition::HAVE_BUILDING: - { - if (condition.object) // specific town - { - const CGTownInstance *t = static_cast(condition.object); - return (t->tempOwner == player && t->hasBuilt(BuildingID(condition.objectType))); - } - else // any town - { - for (const CGTownInstance * t : p->towns) - { - if (t->hasBuilt(BuildingID(condition.objectType))) - return true; - } - return false; - } - } - case EventCondition::DESTROY: - { - if (condition.object) // mode A - destroy specific object of this type - { - if (auto hero = dynamic_cast(condition.object)) - return boost::range::find(gs->map->heroesOnMap, hero) == gs->map->heroesOnMap.end(); - else - return getObj(condition.object->id) == nullptr; - } - else - { - for(auto & elem : map->objects) // mode B - destroy all objects of this type - { - if(elem && elem->ID == condition.objectType) - return false; - } - return true; - } - } - case EventCondition::CONTROL: - { - // list of players that need to control object to fulfull condition - // NOTE: cgameinfocallback specified explicitly in order to get const version - auto & team = CGameInfoCallback::getPlayerTeam(player)->players; - - if (condition.object) // mode A - flag one specific object, like town - { - return team.count(condition.object->tempOwner) != 0; - } - else - { - for(auto & elem : map->objects) // mode B - flag all objects of this type - { - //check not flagged objs - if ( elem && elem->ID == condition.objectType && team.count(elem->tempOwner) == 0 ) - return false; - } - return true; - } - } - case EventCondition::TRANSPORT: - { - const CGTownInstance *t = static_cast(condition.object); - if((t->visitingHero && t->visitingHero->hasArt(condition.objectType)) - || (t->garrisonHero && t->garrisonHero->hasArt(condition.objectType))) - { - return true; - } - return false; - } - case EventCondition::DAYS_PASSED: - { - return gs->day > condition.value; - } - case EventCondition::IS_HUMAN: - { - return p->human ? condition.value == 1 : condition.value == 0; - } - case EventCondition::DAYS_WITHOUT_TOWN: - { - if (p->daysWithoutCastle) - return p->daysWithoutCastle.get() >= condition.value; - else - return false; - } - case EventCondition::CONST_VALUE: - { - return condition.value; // just convert to bool - } - } - assert(0); - return false; -} - -PlayerColor CGameState::checkForStandardWin() const -{ - //std victory condition is: - //all enemies lost - PlayerColor supposedWinner = PlayerColor::NEUTRAL; - TeamID winnerTeam = TeamID::NO_TEAM; - for(auto & elem : players) - { - if(elem.second.status == EPlayerStatus::INGAME && elem.first < PlayerColor::PLAYER_LIMIT) - { - if(supposedWinner == PlayerColor::NEUTRAL) - { - //first player remaining ingame - candidate for victory - supposedWinner = elem.second.color; - winnerTeam = elem.second.team; - } - else if(winnerTeam != elem.second.team) - { - //current candidate has enemy remaining in game -> no vicotry - return PlayerColor::NEUTRAL; - } - } - } - - return supposedWinner; -} - -bool CGameState::checkForStandardLoss( PlayerColor player ) const -{ - //std loss condition is: player lost all towns and heroes - const PlayerState &p = *CGameInfoCallback::getPlayer(player); - return !p.heroes.size() && !p.towns.size(); -} - -struct statsHLP -{ - typedef std::pair< PlayerColor, si64 > TStat; - //converts [] to vec[place] -> platers - static std::vector< std::vector< PlayerColor > > getRank( std::vector stats ) - { - std::sort(stats.begin(), stats.end(), statsHLP()); - - //put first element - std::vector< std::vector > ret; - std::vector tmp; - tmp.push_back( stats[0].first ); - ret.push_back( tmp ); - - //the rest of elements - for(int g=1; gpush_back( stats[g].first ); - } - else - { - //create next occupied rank - std::vector tmp; - tmp.push_back(stats[g].first); - ret.push_back(tmp); - } - } - - return ret; - } - - bool operator()(const TStat & a, const TStat & b) const - { - return a.second > b.second; - } - - static const CGHeroInstance * findBestHero(CGameState * gs, PlayerColor color) - { - std::vector > &h = gs->players[color].heroes; - if(!h.size()) - return nullptr; - //best hero will be that with highest exp - int best = 0; - for(int b=1; bexp > h[best]->exp) - { - best = b; - } - } - return h[best]; - } - - //calculates total number of artifacts that belong to given player - static int getNumberOfArts(const PlayerState * ps) - { - int ret = 0; - for(auto h : ps->heroes) - { - ret += h->artifactsInBackpack.size() + h->artifactsWorn.size(); - } - return ret; - } - - // get total strength of player army - static si64 getArmyStrength(const PlayerState * ps) - { - si64 str = 0; - - for(auto h : ps->heroes) - { - if(!h->inTownGarrison) //original h3 behavior - str += h->getArmyStrength(); - } - return str; - } -}; - -void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) -{ - auto playerInactive = [&](PlayerColor color) - { - return color == PlayerColor::NEUTRAL || players.at(color).status != EPlayerStatus::INGAME; - }; - -#define FILL_FIELD(FIELD, VAL_GETTER) \ - { \ - std::vector< std::pair< PlayerColor, si64 > > stats; \ - for(auto g = players.begin(); g != players.end(); ++g) \ - { \ - if(playerInactive(g->second.color)) \ - continue; \ - std::pair< PlayerColor, si64 > stat; \ - stat.first = g->second.color; \ - stat.second = VAL_GETTER; \ - stats.push_back(stat); \ - } \ - tgi.FIELD = statsHLP::getRank(stats); \ - } - - for(auto & elem : players) - { - if(!playerInactive(elem.second.color)) - tgi.playerColors.push_back(elem.second.color); - } - - if(level >= 1) //num of towns & num of heroes - { - //num of towns - FILL_FIELD(numOfTowns, g->second.towns.size()) - //num of heroes - FILL_FIELD(numOfHeroes, g->second.heroes.size()) - //best hero's portrait - for(auto g = players.cbegin(); g != players.cend(); ++g) - { - if(playerInactive(g->second.color)) - continue; - const CGHeroInstance * best = statsHLP::findBestHero(this, g->second.color); - InfoAboutHero iah; - iah.initFromHero(best, level >= 8); - iah.army.clear(); - tgi.colorToBestHero[g->second.color] = iah; - } - } - if(level >= 2) //gold - { - FILL_FIELD(gold, g->second.resources[Res::GOLD]) - } - if(level >= 2) //wood & ore - { - FILL_FIELD(woodOre, g->second.resources[Res::WOOD] + g->second.resources[Res::ORE]) - } - if(level >= 3) //mercury, sulfur, crystal, gems - { - FILL_FIELD(mercSulfCrystGems, g->second.resources[Res::MERCURY] + g->second.resources[Res::SULFUR] + g->second.resources[Res::CRYSTAL] + g->second.resources[Res::GEMS]) - } - if(level >= 4) //obelisks found - { - FILL_FIELD(obelisks, CGObelisk::visited[gs->getPlayerTeam(g->second.color)->id]) - } - if(level >= 5) //artifacts - { - FILL_FIELD(artifacts, statsHLP::getNumberOfArts(&g->second)) - } - if(level >= 6) //army strength - { - FILL_FIELD(army, statsHLP::getArmyStrength(&g->second)) - } - if(level >= 7) //income - { - //TODO:obtainPlayersStats - income - } - if(level >= 8) //best hero's stats - { - //already set in lvl 1 handling - } - if(level >= 9) //personality - { - for(auto g = players.cbegin(); g != players.cend(); ++g) - { - if(playerInactive(g->second.color)) //do nothing for neutral player - continue; - if(g->second.human) - { - tgi.personality[g->second.color] = EAiTactic::NONE; - } - else //AI - { - tgi.personality[g->second.color] = map->players[g->second.color.getNum()].aiTactic; - } - - } - } - if(level >= 10) //best creature - { - //best creatures belonging to player (highest AI value) - for(auto g = players.cbegin(); g != players.cend(); ++g) - { - if(playerInactive(g->second.color)) //do nothing for neutral player - continue; - int bestCre = -1; //best creature's ID - for(auto & elem : g->second.heroes) - { - for(auto it = elem->Slots().begin(); it != elem->Slots().end(); ++it) - { - int toCmp = it->second->type->idNumber; //ID of creature we should compare with the best one - if(bestCre == -1 || VLC->creh->creatures[bestCre]->AIValue < VLC->creh->creatures[toCmp]->AIValue) - { - bestCre = toCmp; - } - } - } - tgi.bestCreature[g->second.color] = bestCre; - } - } - -#undef FILL_FIELD -} - -std::map > CGameState::unusedHeroesFromPool() -{ - std::map > pool = hpool.heroesPool; - for ( auto i = players.cbegin() ; i != players.cend();i++) - for(auto j = i->second.availableHeroes.cbegin(); j != i->second.availableHeroes.cend(); j++) - if(*j) - pool.erase((**j).subID); - - return pool; -} - -void CGameState::buildBonusSystemTree() -{ - buildGlobalTeamPlayerTree(); - attachArmedObjects(); - - for(CGTownInstance *t : map->towns) - { - t->deserializationFix(); - } - // CStackInstance <-> CCreature, CStackInstance <-> CArmedInstance, CArtifactInstance <-> CArtifact - // are provided on initializing / deserializing -} - -void CGameState::deserializationFix() -{ - buildGlobalTeamPlayerTree(); - attachArmedObjects(); -} - -void CGameState::buildGlobalTeamPlayerTree() -{ - for(auto k=teams.begin(); k!=teams.end(); ++k) - { - TeamState *t = &k->second; - t->attachTo(&globalEffects); - - for(PlayerColor teamMember : k->second.players) - { - PlayerState *p = getPlayer(teamMember); - assert(p); - p->attachTo(t); - } - } -} - -void CGameState::attachArmedObjects() -{ - for(CGObjectInstance *obj : map->objects) - { - if(CArmedInstance *armed = dynamic_cast(obj)) - armed->whatShouldBeAttached()->attachTo(armed->whereShouldBeAttached(this)); - } -} - -void CGameState::giveHeroArtifact(CGHeroInstance *h, ArtifactID aid) -{ - CArtifact * const artifact = VLC->arth->artifacts[aid]; //pointer to constant object - CArtifactInstance *ai = CArtifactInstance::createNewArtifactInstance(artifact); - map->addNewArtifactInstance(ai); - ai->putAt(ArtifactLocation(h, ai->firstAvailableSlot(h))); -} - -std::set CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllowed /*= false*/) const -{ - std::set ret; - for(int i = 0; i < map->allowedHeroes.size(); i++) - if(map->allowedHeroes[i] || alsoIncludeNotAllowed) - ret.insert(HeroTypeID(i)); - - for(auto hero : map->heroesOnMap) //heroes instances initialization - { - if(hero->type) - ret -= hero->type->ID; - else - ret -= HeroTypeID(hero->subID); - } - - for(auto obj : map->objects) //prisons - if(obj && obj->ID == Obj::PRISON) - ret -= HeroTypeID(obj->subID); - - return ret; -} - -std::vector CGameState::generateCampaignHeroesToReplace(CrossoverHeroesList & crossoverHeroes) -{ - std::vector campaignHeroReplacements; - - //selecting heroes by type - for(auto obj : map->objects) - { - if(obj && obj->ID == Obj::HERO_PLACEHOLDER) - { - auto heroPlaceholder = dynamic_cast(obj.get()); - if(heroPlaceholder->subID != 0xFF) //select by type - { - auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [heroPlaceholder](CGHeroInstance * hero) - { - return hero->subID == heroPlaceholder->subID; - }); - - if(it != crossoverHeroes.heroesFromAnyPreviousScenarios.end()) - { - auto hero = *it; - crossoverHeroes.removeHeroFromBothLists(hero); - campaignHeroReplacements.push_back(CampaignHeroReplacement(CMemorySerializer::deepCopy(*hero).release(), heroPlaceholder->id)); - } - } - } - } - - //selecting heroes by power - range::sort(crossoverHeroes.heroesFromPreviousScenario, [](const CGHeroInstance * a, const CGHeroInstance * b) - { - return a->getHeroStrength() > b->getHeroStrength(); - }); //sort, descending strength - - // sort hero placeholders descending power - std::vector heroPlaceholders; - for(auto obj : map->objects) - { - if(obj && obj->ID == Obj::HERO_PLACEHOLDER) - { - auto heroPlaceholder = dynamic_cast(obj.get()); - if(heroPlaceholder->subID == 0xFF) //select by power - { - heroPlaceholders.push_back(heroPlaceholder); - } - } - } - range::sort(heroPlaceholders, [](const CGHeroPlaceholder * a, const CGHeroPlaceholder * b) - { - return a->power > b->power; - }); - - for(int i = 0; i < heroPlaceholders.size(); ++i) - { - auto heroPlaceholder = heroPlaceholders[i]; - if(crossoverHeroes.heroesFromPreviousScenario.size() > i) - { - auto hero = crossoverHeroes.heroesFromPreviousScenario[i]; - campaignHeroReplacements.push_back(CampaignHeroReplacement(CMemorySerializer::deepCopy(*hero).release(), heroPlaceholder->id)); - } - } - - return campaignHeroReplacements; -} - -void CGameState::replaceHeroesPlaceholders(const std::vector & campaignHeroReplacements) -{ - for(auto campaignHeroReplacement : campaignHeroReplacements) - { - auto heroPlaceholder = dynamic_cast(getObjInstance(campaignHeroReplacement.heroPlaceholderId)); - - CGHeroInstance *heroToPlace = campaignHeroReplacement.hero; - heroToPlace->id = campaignHeroReplacement.heroPlaceholderId; - heroToPlace->tempOwner = heroPlaceholder->tempOwner; - heroToPlace->pos = heroPlaceholder->pos; - heroToPlace->type = VLC->heroh->heroes[heroToPlace->subID]; - - for(auto &&i : heroToPlace->stacks) - i.second->type = VLC->creh->creatures[i.second->getCreatureID()]; - - auto fixArtifact = [&](CArtifactInstance * art) - { - art->artType = VLC->arth->artifacts[art->artType->id]; - gs->map->artInstances.push_back(art); - art->id = ArtifactInstanceID(gs->map->artInstances.size() - 1); - }; - - for(auto &&i : heroToPlace->artifactsWorn) - fixArtifact(i.second.artifact); - for(auto &&i : heroToPlace->artifactsInBackpack) - fixArtifact(i.artifact); - - map->heroesOnMap.push_back(heroToPlace); - map->objects[heroToPlace->id.getNum()] = heroToPlace; - map->addBlockVisTiles(heroToPlace); - - scenarioOps->campState->getCurrentScenario().placedCrossoverHeroes.push_back(heroToPlace); - } -} - -bool CGameState::isUsedHero(HeroTypeID hid) const -{ - return getUsedHero(hid); -} - -CGHeroInstance * CGameState::getUsedHero(HeroTypeID hid) const -{ - for(auto hero : map->heroesOnMap) //heroes instances initialization - { - if(hero->subID == hid.getNum()) - { - return hero; - } - } - - for(auto obj : map->objects) //prisons - { - if(obj && obj->ID == Obj::PRISON && obj->subID == hid.getNum()) - { - return dynamic_cast(obj.get()); - } - } - - return nullptr; -} - -CGPathNode::CGPathNode() -:coord(-1,-1,-1) -{ - accessible = NOT_SET; - land = 0; - moveRemains = 0; - turns = 255; - theNodeBefore = nullptr; -} - -bool CGPathNode::reachable() const -{ - return turns < 255; -} - -bool CPathsInfo::getPath( const int3 &dst, CGPath &out ) -{ - assert(isValid); - - out.nodes.clear(); - const CGPathNode *curnode = &nodes[dst.x][dst.y][dst.z]; - if(!curnode->theNodeBefore) - return false; - - - while(curnode) - { - CGPathNode cpn = *curnode; - curnode = curnode->theNodeBefore; - out.nodes.push_back(cpn); - } - return true; -} - -CPathsInfo::CPathsInfo( const int3 &Sizes ) -:sizes(Sizes) -{ - hero = nullptr; - nodes = new CGPathNode**[sizes.x]; - for(int i = 0; i < sizes.x; i++) - { - nodes[i] = new CGPathNode*[sizes.y]; - for (int j = 0; j < sizes.y; j++) - { - nodes[i][j] = new CGPathNode[sizes.z]; - } - } -} - -CPathsInfo::~CPathsInfo() -{ - for(int i = 0; i < sizes.x; i++) - { - for (int j = 0; j < sizes.y; j++) - { - delete [] nodes[i][j]; - } - delete [] nodes[i]; - } - delete [] nodes; -} - -int3 CGPath::startPos() const -{ - return nodes[nodes.size()-1].coord; -} - -int3 CGPath::endPos() const -{ - return nodes[0].coord; -} - -void CGPath::convert( ui8 mode ) -{ - if(mode==0) - { - for(auto & elem : nodes) - { - elem.coord = CGHeroInstance::convertPosition(elem.coord,true); - } - } -} - -PlayerState::PlayerState() - : color(-1), currentSelection(0xffffffff), enteredWinningCheatCode(0), - enteredLosingCheatCode(0), status(EPlayerStatus::INGAME) -{ - setNodeType(PLAYER); -} - -std::string PlayerState::nodeName() const -{ - return "Player " + (color.getNum() < VLC->generaltexth->capColors.size() ? VLC->generaltexth->capColors[color.getNum()] : boost::lexical_cast(color)); -} - -InfoAboutArmy::InfoAboutArmy(): - owner(PlayerColor::NEUTRAL) -{} - -InfoAboutArmy::InfoAboutArmy(const CArmedInstance *Army, bool detailed) -{ - initFromArmy(Army, detailed); -} - -void InfoAboutArmy::initFromArmy(const CArmedInstance *Army, bool detailed) -{ - army = ArmyDescriptor(Army, detailed); - owner = Army->tempOwner; - name = Army->getHoverText(); -} - -void InfoAboutHero::assign(const InfoAboutHero & iah) -{ - InfoAboutArmy::operator = (iah); - - details = (iah.details ? new Details(*iah.details) : nullptr); - hclass = iah.hclass; - portrait = iah.portrait; -} - -InfoAboutHero::InfoAboutHero(): - details(nullptr), - hclass(nullptr), - portrait(-1) -{} - -InfoAboutHero::InfoAboutHero(const InfoAboutHero & iah): - InfoAboutArmy() -{ - assign(iah); -} - -InfoAboutHero::InfoAboutHero(const CGHeroInstance *h, bool detailed) - : details(nullptr), - hclass(nullptr), - portrait(-1) -{ - initFromHero(h, detailed); -} - -InfoAboutHero::~InfoAboutHero() -{ - delete details; -} - -InfoAboutHero & InfoAboutHero::operator=(const InfoAboutHero & iah) -{ - assign(iah); - return *this; -} - -void InfoAboutHero::initFromHero(const CGHeroInstance *h, bool detailed) -{ - if(!h) - return; - - initFromArmy(h, detailed); - - hclass = h->type->heroClass; - name = h->name; - portrait = h->portrait; - - if(detailed) - { - //include details about hero - details = new Details; - details->luck = h->LuckVal(); - details->morale = h->MoraleVal(); - details->mana = h->mana; - details->primskills.resize(GameConstants::PRIMARY_SKILLS); - - for (int i = 0; i < GameConstants::PRIMARY_SKILLS ; i++) - { - details->primskills[i] = h->getPrimSkillLevel(static_cast(i)); - } - } -} - -InfoAboutTown::InfoAboutTown(): - details(nullptr), - tType(nullptr), - built(0), - fortLevel(0) -{ - -} - -InfoAboutTown::InfoAboutTown(const CGTownInstance *t, bool detailed) -{ - initFromTown(t, detailed); -} - -InfoAboutTown::~InfoAboutTown() -{ - delete details; -} - -void InfoAboutTown::initFromTown(const CGTownInstance *t, bool detailed) -{ - initFromArmy(t, detailed); - army = ArmyDescriptor(t->getUpperArmy(), detailed); - built = t->builded; - fortLevel = t->fortLevel(); - name = t->name; - tType = t->town; - - if(detailed) - { - //include details about hero - details = new Details; - TResources income = t->dailyIncome(); - details->goldIncome = income[Res::GOLD]; - details->customRes = t->hasBuilt(BuildingID::RESOURCE_SILO); - details->hallLevel = t->hallLevel(); - details->garrisonedHero = t->garrisonHero; - } -} - -ArmyDescriptor::ArmyDescriptor(const CArmedInstance *army, bool detailed) - : isDetailed(detailed) -{ - for(auto & elem : army->Slots()) - { - if(detailed) - (*this)[elem.first] = *elem.second; - else - (*this)[elem.first] = CStackBasicDescriptor(elem.second->type, elem.second->getQuantityID()); - } -} - -ArmyDescriptor::ArmyDescriptor() - : isDetailed(false) -{ - -} - -int ArmyDescriptor::getStrength() const -{ - ui64 ret = 0; - if(isDetailed) - { - for(auto & elem : *this) - ret += elem.second.type->AIValue * elem.second.count; - } - else - { - for(auto & elem : *this) - ret += elem.second.type->AIValue * CCreature::estimateCreatureCount(elem.second.count); - } - return ret; -} - -DuelParameters::SideSettings::StackSettings::StackSettings() - : type(CreatureID::NONE), count(0) -{ -} - -DuelParameters::SideSettings::StackSettings::StackSettings(CreatureID Type, si32 Count) - : type(Type), count(Count) -{ -} - -DuelParameters::SideSettings::SideSettings() -{ - heroId = -1; -} - -DuelParameters::DuelParameters(): - terType(ETerrainType::DIRT), - bfieldType(BFieldType::ROCKLANDS) -{ -} - -DuelParameters DuelParameters::fromJSON(const std::string &fname) -{ - DuelParameters ret; - - const JsonNode duelData(ResourceID("DATA/" + fname, EResType::TEXT)); - ret.terType = ETerrainType((int)duelData["terType"].Float()); - ret.bfieldType = BFieldType((int)duelData["bfieldType"].Float()); - for(const JsonNode &n : duelData["sides"].Vector()) - { - SideSettings &ss = ret.sides[(int)n["side"].Float()]; - int i = 0; - for(const JsonNode &stackNode : n["army"].Vector()) - { - ss.stacks[i].type = CreatureID((si32)stackNode.Vector()[0].Float()); - ss.stacks[i].count = stackNode.Vector()[1].Float(); - i++; - } - - if(n["heroid"].getType() == JsonNode::DATA_FLOAT) - ss.heroId = n["heroid"].Float(); - else - ss.heroId = -1; - - for(const JsonNode &entry : n["heroPrimSkills"].Vector()) - ss.heroPrimSkills.push_back(entry.Float()); - - for(const JsonNode &skillNode : n["heroSecSkills"].Vector()) - { - std::pair secSkill; - secSkill.first = skillNode.Vector()[0].Float(); - secSkill.second = skillNode.Vector()[1].Float(); - ss.heroSecSkills.push_back(secSkill); - } - - assert(ss.heroPrimSkills.empty() || ss.heroPrimSkills.size() == GameConstants::PRIMARY_SKILLS); - - if(ss.heroId != -1) - { - const JsonNode & spells = n["spells"]; - if(spells.getType() == JsonNode::DATA_STRING && spells.String() == "all") - { - for(auto spell : VLC->spellh->objects) - if(spell->id <= SpellID::SUMMON_AIR_ELEMENTAL) - ss.spells.insert(spell->id); - } - else - for(const JsonNode &spell : n["spells"].Vector()) - ss.spells.insert(SpellID(spell.Float())); - } - } - - for(const JsonNode &n : duelData["obstacles"].Vector()) - { - auto oi = make_shared(); - if(n.getType() == JsonNode::DATA_VECTOR) - { - oi->ID = n.Vector()[0].Float(); - oi->pos = n.Vector()[1].Float(); - } - else - { - assert(n.getType() == JsonNode::DATA_FLOAT); - oi->ID = 21; - oi->pos = n.Float(); - } - oi->uniqueID = ret.obstacles.size(); - ret.obstacles.push_back(oi); - } - - for(const JsonNode &n : duelData["creatures"].Vector()) - { - CusomCreature cc; - cc.id = n["id"].Float(); - -#define retrieve(name) \ - if(n[ #name ].getType() == JsonNode::DATA_FLOAT)\ - cc.name = n[ #name ].Float(); \ - else \ - cc.name = -1; - - retrieve(attack); - retrieve(defense); - retrieve(HP); - retrieve(dmg); - retrieve(shoots); - retrieve(speed); - ret.creatures.push_back(cc); - } - - return ret; -} - -TeamState::TeamState() -{ - setNodeType(TEAM); -} - -void CPathfinder::initializeGraph() -{ - CGPathNode ***graph = out.nodes; - for(size_t i=0; i < out.sizes.x; ++i) - { - for(size_t j=0; j < out.sizes.y; ++j) - { - for(size_t k=0; k < out.sizes.z; ++k) - { - curPos = int3(i,j,k); - const TerrainTile *tinfo = &gs->map->getTile(int3(i, j, k)); - CGPathNode &node = graph[i][j][k]; - - node.accessible = evaluateAccessibility(tinfo); - node.turns = 0xff; - node.moveRemains = 0; - node.coord.x = i; - node.coord.y = j; - node.coord.z = k; - node.land = tinfo->terType != ETerrainType::WATER; - node.theNodeBefore = nullptr; - } - } - } -} - -void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*= -1*/) -{ - assert(hero); - assert(hero == getHero(hero->id)); - if(src.x < 0) - src = hero->getPosition(false); - if(movement < 0) - movement = hero->movement; - bool flying = hero->hasBonusOfType(Bonus::FLYING_MOVEMENT); - int maxMovePointsLand = hero->maxMovePoints(true); - int maxMovePointsWater = hero->maxMovePoints(false); - - auto maxMovePoints = [&](CGPathNode *cp) -> int - { - return cp->land ? maxMovePointsLand : maxMovePointsWater; - }; - - out.hero = hero; - out.hpos = src; - - if(!gs->map->isInTheMap(src)/* || !gs->map->isInTheMap(dest)*/) //check input - { - logGlobal->errorStream() << "CGameState::calculatePaths: Hero outside the gs->map? How dare you..."; - return; - } - - initializeGraph(); - - - //initial tile - set cost on 0 and add to the queue - CGPathNode &initialNode = *getNode(src); - initialNode.turns = 0; - initialNode.moveRemains = movement; - mq.push_back(&initialNode); - - std::vector neighbours; - neighbours.reserve(16); - while(!mq.empty()) - { - cp = mq.front(); - mq.pop_front(); - - const int3 sourceGuardPosition = gs->map->guardingCreaturePositions[cp->coord.x][cp->coord.y][cp->coord.z]; - bool guardedSource = (sourceGuardPosition != int3(-1, -1, -1) && cp->coord != src); - ct = &gs->map->getTile(cp->coord); - - int movement = cp->moveRemains, turn = cp->turns; - if(!movement) - { - movement = maxMovePoints(cp); - turn++; - } - - - //add accessible neighbouring nodes to the queue - neighbours.clear(); - - //handling subterranean gate => it's exit is the only neighbour - bool subterraneanEntry = (ct->topVisitableId() == Obj::SUBTERRANEAN_GATE && useSubterraneanGates); - if(subterraneanEntry) - { - //try finding the exit gate - if(const CGObjectInstance *outGate = getObj(CGTeleport::getMatchingGate(ct->visitableObjects.back()->id), false)) - { - const int3 outPos = outGate->visitablePos(); - //gs->getNeighbours(*getTile(outPos), outPos, neighbours, boost::logic::indeterminate, !cp->land); - neighbours.push_back(outPos); - } - else - { - //gate with no exit (blocked) -> do nothing with this node - continue; - } - } - - gs->getNeighbours(*ct, cp->coord, neighbours, boost::logic::indeterminate, !cp->land); - - for(auto & neighbour : neighbours) - { - const int3 &n = neighbour; //current neighbor - dp = getNode(n); - dt = &gs->map->getTile(n); - destTopVisObjID = dt->topVisitableId(); - - useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark - - int turnAtNextTile = turn; - - - const bool destIsGuardian = sourceGuardPosition == n; - - if(!goodForLandSeaTransition()) - continue; - - if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED ) - continue; - - //special case -> hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile - if(cp->accessible == CGPathNode::VISITABLE && guardedSource && cp->theNodeBefore->land && ct->topVisitableId() == Obj::BOAT) - guardedSource = false; - - int cost = gs->getMovementCost(hero, cp->coord, dp->coord, flying, movement); - - //special case -> moving from src Subterranean gate to dest gate -> it's free - if(subterraneanEntry && destTopVisObjID == Obj::SUBTERRANEAN_GATE && cp->coord.z != dp->coord.z) - cost = 0; - - int remains = movement - cost; - - if(useEmbarkCost) - { - remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1); - cost = movement - remains; - } - - if(remains < 0) - { - //occurs rarely, when hero with low movepoints tries to leave the road - turnAtNextTile++; - int moveAtNextTile = maxMovePoints(cp); - cost = gs->getMovementCost(hero, cp->coord, dp->coord, flying, moveAtNextTile); //cost must be updated, movement points changed :( - remains = moveAtNextTile - cost; - } - - if((dp->turns==0xff //we haven't been here before - || dp->turns > turnAtNextTile - || (dp->turns >= turnAtNextTile && dp->moveRemains < remains)) //this route is faster - && (!guardedSource || destIsGuardian)) // Can step into tile of guard - { - - assert(dp != cp->theNodeBefore); //two tiles can't point to each other - dp->moveRemains = remains; - dp->turns = turnAtNextTile; - dp->theNodeBefore = cp; - - const bool guardedDst = gs->map->guardingCreaturePositions[dp->coord.x][dp->coord.y][dp->coord.z].valid() - && dp->accessible == CGPathNode::BLOCKVIS; - - if (dp->accessible == CGPathNode::ACCESSIBLE - || (useEmbarkCost && allowEmbarkAndDisembark) - || destTopVisObjID == Obj::SUBTERRANEAN_GATE - || (guardedDst && !guardedSource)) // Can step into a hostile tile once. - { - mq.push_back(dp); - } - } - } //neighbours loop - } //queue loop - - out.isValid = true; -} - -CGPathNode *CPathfinder::getNode(const int3 &coord) -{ - return &out.nodes[coord.x][coord.y][coord.z]; -} - -bool CPathfinder::canMoveBetween(const int3 &a, const int3 &b) const -{ - return gs->checkForVisitableDir(a, b) && gs->checkForVisitableDir(b, a); -} - -CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const TerrainTile *tinfo) const -{ - CGPathNode::EAccessibility ret = (tinfo->blocked ? CGPathNode::BLOCKED : CGPathNode::ACCESSIBLE); - - - if(tinfo->terType == ETerrainType::ROCK || !FoW[curPos.x][curPos.y][curPos.z]) - return CGPathNode::BLOCKED; - - if(tinfo->visitable) - { - if(tinfo->visitableObjects.front()->ID == Obj::SANCTUARY && tinfo->visitableObjects.back()->ID == Obj::HERO && tinfo->visitableObjects.back()->tempOwner != hero->tempOwner) //non-owned hero stands on Sanctuary - { - return CGPathNode::BLOCKED; - } - else - { - for(const CGObjectInstance *obj : tinfo->visitableObjects) - { - if(obj->passableFor(hero->tempOwner)) //special object instance specific passableness flag - overwrites other accessibility flags - { - ret = CGPathNode::ACCESSIBLE; - } - else if(obj->blockVisit) - { - return CGPathNode::BLOCKVIS; - } - else if(obj->ID != Obj::EVENT) //pathfinder should ignore placed events - { - ret = CGPathNode::VISITABLE; - } - } - } - } - else if (gs->map->guardingCreaturePositions[curPos.x][curPos.y][curPos.z].valid() - && !tinfo->blocked) - { - // Monster close by; blocked visit for battle. - return CGPathNode::BLOCKVIS; - } - - return ret; -} - -bool CPathfinder::goodForLandSeaTransition() -{ - if(cp->land != dp->land) //hero can traverse land<->sea only in special circumstances - { - if(cp->land) //from land to sea -> embark or assault hero on boat - { - if(dp->accessible == CGPathNode::ACCESSIBLE || destTopVisObjID < 0) //cannot enter empty water tile from land -> it has to be visitable - return false; - if(destTopVisObjID != Obj::HERO && destTopVisObjID != Obj::BOAT) //only boat or hero can be accessed from land - return false; - if(destTopVisObjID == Obj::BOAT) - useEmbarkCost = 1; - } - else //disembark - { - //can disembark only on coastal tiles - if(!dt->isCoastal()) - return false; - - //tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast - if( (dp->accessible != CGPathNode::ACCESSIBLE && (dp->accessible != CGPathNode::BLOCKVIS || dt->blocked)) - || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate - return false;; - - useEmbarkCost = 2; - } - } - - return true; -} - -CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) : CGameInfoCallback(_gs, boost::optional()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap) -{ - useSubterraneanGates = true; - allowEmbarkAndDisembark = true; -} - -EVictoryLossCheckResult::EVictoryLossCheckResult() : - intValue(0) -{ -} - -EVictoryLossCheckResult::EVictoryLossCheckResult(si32 intValue, std::string toSelf, std::string toOthers) : - messageToSelf(toSelf), - messageToOthers(toOthers), - intValue(intValue) -{ -} - -bool EVictoryLossCheckResult::operator==(EVictoryLossCheckResult const & other) const -{ - return intValue == other.intValue; -} - -bool EVictoryLossCheckResult::operator!=(EVictoryLossCheckResult const & other) const -{ - return intValue != other.intValue; -} - -bool EVictoryLossCheckResult::victory() const -{ - return intValue == VICTORY; -} - -bool EVictoryLossCheckResult::loss() const -{ - return intValue == DEFEAT; -} - -EVictoryLossCheckResult EVictoryLossCheckResult::invert() -{ - return EVictoryLossCheckResult(-intValue, messageToOthers, messageToSelf); -} - -EVictoryLossCheckResult EVictoryLossCheckResult::victory(std::string toSelf, std::string toOthers) -{ - return EVictoryLossCheckResult(VICTORY, toSelf, toOthers); -} - -EVictoryLossCheckResult EVictoryLossCheckResult::defeat(std::string toSelf, std::string toOthers) -{ - return EVictoryLossCheckResult(DEFEAT, toSelf, toOthers); -} - -std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult) -{ - os << victoryLossCheckResult.messageToSelf; - return os; -} - -CRandomGenerator & CGameState::getRandomGenerator() -{ - return rand; -} +#include "StdInc.h" +#include "CGameState.h" + +#include "mapping/CCampaignHandler.h" +#include "mapObjects/CObjectClassesHandler.h" +#include "CArtHandler.h" +#include "CBuildingHandler.h" +#include "CGeneralTextHandler.h" +#include "CTownHandler.h" +#include "CSpellHandler.h" +#include "CHeroHandler.h" +#include "mapObjects/CObjectHandler.h" +#include "CCreatureHandler.h" +#include "CModHandler.h" +#include "VCMI_Lib.h" +#include "Connection.h" +#include "mapping/CMap.h" +#include "mapping/CMapService.h" +#include "StartInfo.h" +#include "NetPacks.h" +#include "registerTypes/RegisterTypes.h" +#include "mapping/CMapInfo.h" +#include "BattleState.h" +#include "JsonNode.h" +#include "filesystem/Filesystem.h" +#include "GameConstants.h" +#include "rmg/CMapGenerator.h" +#include "CStopWatch.h" +#include "mapping/CMapEditManager.h" + +class CGObjectInstance; + +#ifdef min +#undef min +#endif +#ifdef max +#undef max +#endif + +/* + * CGameState.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +template class CApplyOnGS; + +class CBaseForGSApply +{ +public: + virtual void applyOnGS(CGameState *gs, void *pack) const =0; + virtual ~CBaseForGSApply(){}; + template static CBaseForGSApply *getApplier(const U * t=nullptr) + { + return new CApplyOnGS; + } +}; + +template class CApplyOnGS : public CBaseForGSApply +{ +public: + void applyOnGS(CGameState *gs, void *pack) const + { + T *ptr = static_cast(pack); + + boost::unique_lock lock(*gs->mx); + ptr->applyGs(gs); + } +}; + +static CApplier *applierGs = nullptr; + +// class IObjectCaller +// { +// public: +// virtual ~IObjectCaller(){}; +// virtual void preInit()=0; +// virtual void postInit()=0; +// }; +// +// template +// class CObjectCaller : public IObjectCaller +// { +// public: +// void preInit() +// { +// //T::preInit(); +// } +// void postInit() +// { +// //T::postInit(); +// } +// }; + +// class CObjectCallersHandler +// { +// public: +// std::vector apps; +// +// template void registerType(const T * t=nullptr) +// { +// apps.push_back(new CObjectCaller); +// } +// +// CObjectCallersHandler() +// { +// registerTypesMapObjects(*this); +// } +// +// ~CObjectCallersHandler() +// { +// for (auto & elem : apps) +// delete elem; +// } +// +// void preInit() +// { +// // for (size_t i = 0; i < apps.size(); i++) +// // apps[i]->preInit(); +// } +// +// void postInit() +// { +// //for (size_t i = 0; i < apps.size(); i++) +// //apps[i]->postInit(); +// } +// } *objCaller = nullptr; + +void MetaString::getLocalString(const std::pair &txt, std::string &dst) const +{ + int type = txt.first, ser = txt.second; + + if(type == ART_NAMES) + { + dst = VLC->arth->artifacts[ser]->Name(); + } + else if(type == CRE_PL_NAMES) + { + dst = VLC->creh->creatures[ser]->namePl; + } + else if(type == MINE_NAMES) + { + dst = VLC->generaltexth->mines[ser].first; + } + else if(type == MINE_EVNTS) + { + dst = VLC->generaltexth->mines[ser].second; + } + else if(type == SPELL_NAME) + { + dst = SpellID(ser).toSpell()->name; + } + else if(type == CRE_SING_NAMES) + { + dst = VLC->creh->creatures[ser]->nameSing; + } + else if(type == ART_DESCR) + { + dst = VLC->arth->artifacts[ser]->Description(); + } + else if (type == ART_EVNTS) + { + dst = VLC->arth->artifacts[ser]->EventText(); + } + else if (type == OBJ_NAMES) + { + dst = VLC->objtypeh->getObjectName(ser); + } + else + { + std::vector *vec; + switch(type) + { + case GENERAL_TXT: + vec = &VLC->generaltexth->allTexts; + break; + case XTRAINFO_TXT: + vec = &VLC->generaltexth->xtrainfo; + break; + case RES_NAMES: + vec = &VLC->generaltexth->restypes; + break; + case ARRAY_TXT: + vec = &VLC->generaltexth->arraytxt; + break; + case CREGENS: + vec = &VLC->generaltexth->creGens; + break; + case CREGENS4: + vec = &VLC->generaltexth->creGens4; + break; + case ADVOB_TXT: + vec = &VLC->generaltexth->advobtxt; + break; + case SEC_SKILL_NAME: + vec = &VLC->generaltexth->skillName; + break; + case COLOR: + vec = &VLC->generaltexth->capColors; + break; + default: + logGlobal->errorStream() << "Failed string substitution because type is " << type; + dst = "#@#"; + return; + } + if(vec->size() <= ser) + { + logGlobal->errorStream() << "Failed string substitution with type " << type << " because index " << ser << " is out of bounds!"; + dst = "#!#"; + } + else + dst = (*vec)[ser]; + } +} + +DLL_LINKAGE void MetaString::toString(std::string &dst) const +{ + size_t exSt = 0, loSt = 0, nums = 0; + dst.clear(); + + for(auto & elem : message) + {//TEXACT_STRING, TLOCAL_STRING, TNUMBER, TREPLACE_ESTRING, TREPLACE_LSTRING, TREPLACE_NUMBER + switch(elem) + { + case TEXACT_STRING: + dst += exactStrings[exSt++]; + break; + case TLOCAL_STRING: + { + std::string hlp; + getLocalString(localStrings[loSt++], hlp); + dst += hlp; + } + break; + case TNUMBER: + dst += boost::lexical_cast(numbers[nums++]); + break; + case TREPLACE_ESTRING: + boost::replace_first(dst, "%s", exactStrings[exSt++]); + break; + case TREPLACE_LSTRING: + { + std::string hlp; + getLocalString(localStrings[loSt++], hlp); + boost::replace_first(dst, "%s", hlp); + } + break; + case TREPLACE_NUMBER: + boost::replace_first(dst, "%d", boost::lexical_cast(numbers[nums++])); + break; + case TREPLACE_PLUSNUMBER: + boost::replace_first(dst, "%+d", '+' + boost::lexical_cast(numbers[nums++])); + break; + default: + logGlobal->errorStream() << "MetaString processing error!"; + break; + } + } +} + +DLL_LINKAGE std::string MetaString::toString() const +{ + std::string ret; + toString(ret); + return ret; +} + +DLL_LINKAGE std::string MetaString::buildList () const +///used to handle loot from creature bank +{ + + size_t exSt = 0, loSt = 0, nums = 0; + std::string lista; + for (int i = 0; i < message.size(); ++i) + { + if (i > 0 && (message[i] == TEXACT_STRING || message[i] == TLOCAL_STRING)) + { + if (exSt == exactStrings.size() - 1) + lista += VLC->generaltexth->allTexts[141]; //" and " + else + lista += ", "; + } + switch (message[i]) + { + case TEXACT_STRING: + lista += exactStrings[exSt++]; + break; + case TLOCAL_STRING: + { + std::string hlp; + getLocalString (localStrings[loSt++], hlp); + lista += hlp; + } + break; + case TNUMBER: + lista += boost::lexical_cast(numbers[nums++]); + break; + case TREPLACE_ESTRING: + lista.replace (lista.find("%s"), 2, exactStrings[exSt++]); + break; + case TREPLACE_LSTRING: + { + std::string hlp; + getLocalString (localStrings[loSt++], hlp); + lista.replace (lista.find("%s"), 2, hlp); + } + break; + case TREPLACE_NUMBER: + lista.replace (lista.find("%d"), 2, boost::lexical_cast(numbers[nums++])); + break; + default: + logGlobal->errorStream() << "MetaString processing error!"; + } + + } + return lista; +} + + +void MetaString::addCreReplacement(CreatureID id, TQuantity count) //adds sing or plural name; +{ + if (!count) + addReplacement (CRE_PL_NAMES, id); //no creatures - just empty name (eg. defeat Angels) + else if (count == 1) + addReplacement (CRE_SING_NAMES, id); + else + addReplacement (CRE_PL_NAMES, id); +} + +void MetaString::addReplacement(const CStackBasicDescriptor &stack) +{ + assert(stack.type); //valid type + addCreReplacement(stack.type->idNumber, stack.count); +} + +static CGObjectInstance * createObject(Obj id, int subid, int3 pos, PlayerColor owner) +{ + CGObjectInstance * nobj; + switch(id) + { + case Obj::HERO: + { + auto handler = VLC->objtypeh->getHandlerFor(id, VLC->heroh->heroes[subid]->heroClass->id); + nobj = handler->create(handler->getTemplates().front()); + break; + } + case Obj::TOWN: + nobj = new CGTownInstance; + break; + default: //rest of objects + nobj = new CGObjectInstance; + break; + } + nobj->ID = id; + nobj->subID = subid; + nobj->pos = pos; + nobj->tempOwner = owner; + if (id != Obj::HERO) + nobj->appearance = VLC->objtypeh->getHandlerFor(id, subid)->getTemplates().front(); + + return nobj; +} + +CGHeroInstance * CGameState::HeroesPool::pickHeroFor(bool native, PlayerColor player, const CTown *town, + std::map > &available, CRandomGenerator & rand, const CHeroClass * bannedClass /*= nullptr*/) const +{ + CGHeroInstance *ret = nullptr; + + if(player>=PlayerColor::PLAYER_LIMIT) + { + logGlobal->errorStream() << "Cannot pick hero for " << town->faction->index << ". Wrong owner!"; + return nullptr; + } + + std::vector pool; + + if(native) + { + for(auto & elem : available) + { + if(pavailable.find(elem.first)->second & 1<type->heroClass->faction == town->faction->index) + { + pool.push_back(elem.second); //get all available heroes + } + } + if(!pool.size()) + { + logGlobal->errorStream() << "Cannot pick native hero for " << player << ". Picking any..."; + return pickHeroFor(false, player, town, available, rand); + } + else + { + ret = *RandomGeneratorUtil::nextItem(pool, rand); + } + } + else + { + int sum=0, r; + + for(auto & elem : available) + { + if (pavailable.find(elem.first)->second & (1<type->heroClass != bannedClass) ) // and his class is not same as other hero + { + pool.push_back(elem.second); + sum += elem.second->type->heroClass->selectionProbability[town->faction->index]; //total weight + } + } + if(!pool.size() || sum == 0) + { + logGlobal->errorStream() << "There are no heroes available for player " << player<<"!"; + return nullptr; + } + + r = rand.nextInt(sum - 1); + for (auto & elem : pool) + { + r -= elem->type->heroClass->selectionProbability[town->faction->index]; + if(r < 0) + { + ret = elem; + break; + } + } + if(!ret) + ret = pool.back(); + } + + available.erase(ret->subID); + return ret; +} + +void CGameState::CrossoverHeroesList::addHeroToBothLists(CGHeroInstance * hero) +{ + heroesFromPreviousScenario.push_back(hero); + heroesFromAnyPreviousScenarios.push_back(hero); +} + +void CGameState::CrossoverHeroesList::removeHeroFromBothLists(CGHeroInstance * hero) +{ + heroesFromPreviousScenario -= hero; + heroesFromAnyPreviousScenarios -= hero; +} + +CGameState::CampaignHeroReplacement::CampaignHeroReplacement(CGHeroInstance * hero, ObjectInstanceID heroPlaceholderId) : hero(hero), heroPlaceholderId(heroPlaceholderId) +{ + +} + +int CGameState::pickNextHeroType(PlayerColor owner) +{ + const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner); + if(ps.hero >= 0 && !isUsedHero(HeroTypeID(ps.hero))) //we haven't used selected hero + { + return ps.hero; + } + + return pickUnusedHeroTypeRandomly(owner); +} + +int CGameState::pickUnusedHeroTypeRandomly(PlayerColor owner) +{ + //list of available heroes for this faction and others + std::vector factionHeroes, otherHeroes; + + const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner); + for(HeroTypeID hid : getUnusedAllowedHeroes()) + { + if(VLC->heroh->heroes[hid.getNum()]->heroClass->faction == ps.castle) + factionHeroes.push_back(hid); + else + otherHeroes.push_back(hid); + } + + // select random hero native to "our" faction + if(!factionHeroes.empty()) + { + return RandomGeneratorUtil::nextItem(factionHeroes, rand)->getNum(); + } + + logGlobal->warnStream() << "Cannot find free hero of appropriate faction for player " << owner << " - trying to get first available..."; + if(!otherHeroes.empty()) + { + return RandomGeneratorUtil::nextItem(otherHeroes, rand)->getNum(); + } + + logGlobal->errorStream() << "No free allowed heroes!"; + auto notAllowedHeroesButStillBetterThanCrash = getUnusedAllowedHeroes(true); + if(notAllowedHeroesButStillBetterThanCrash.size()) + return notAllowedHeroesButStillBetterThanCrash.begin()->getNum(); + + logGlobal->errorStream() << "No free heroes at all!"; + assert(0); //current code can't handle this situation + return -1; // no available heroes at all +} + +std::pair CGameState::pickObject (CGObjectInstance *obj) +{ + switch(obj->ID) + { + case Obj::RANDOM_ART: + return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE | CArtifact::ART_MINOR | CArtifact::ART_MAJOR | CArtifact::ART_RELIC)); + case Obj::RANDOM_TREASURE_ART: + return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE)); + case Obj::RANDOM_MINOR_ART: + return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MINOR)); + case Obj::RANDOM_MAJOR_ART: + return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MAJOR)); + case Obj::RANDOM_RELIC_ART: + return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(rand, CArtifact::ART_RELIC)); + case Obj::RANDOM_HERO: + return std::make_pair(Obj::HERO, pickNextHeroType(obj->tempOwner)); + case Obj::RANDOM_MONSTER: + return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand)); + case Obj::RANDOM_MONSTER_L1: + return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 1)); + case Obj::RANDOM_MONSTER_L2: + return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 2)); + case Obj::RANDOM_MONSTER_L3: + return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 3)); + case Obj::RANDOM_MONSTER_L4: + return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 4)); + case Obj::RANDOM_RESOURCE: + return std::make_pair(Obj::RESOURCE,rand.nextInt(6)); //now it's OH3 style, use %8 for mithril + case Obj::RANDOM_TOWN: + { + PlayerColor align = PlayerColor((static_cast(obj))->alignment); + si32 f; // can be negative (for random) + if(align >= PlayerColor::PLAYER_LIMIT)//same as owner / random + { + if(obj->tempOwner >= PlayerColor::PLAYER_LIMIT) + f = -1; //random + else + f = scenarioOps->getIthPlayersSettings(obj->tempOwner).castle; + } + else + { + f = scenarioOps->getIthPlayersSettings(align).castle; + } + if(f<0) + { + do + { + f = rand.nextInt(VLC->townh->factions.size() - 1); + } + while (VLC->townh->factions[f]->town == nullptr); // find playable faction + } + return std::make_pair(Obj::TOWN,f); + } + case Obj::RANDOM_MONSTER_L5: + return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 5)); + case Obj::RANDOM_MONSTER_L6: + return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 6)); + case Obj::RANDOM_MONSTER_L7: + return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(rand, 7)); + case Obj::RANDOM_DWELLING: + case Obj::RANDOM_DWELLING_LVL: + case Obj::RANDOM_DWELLING_FACTION: + { + CGDwelling * dwl = static_cast(obj); + int faction; + + //if castle alignment available + if (auto info = dynamic_cast(dwl->info)) + { + faction = rand.nextInt(VLC->townh->factions.size() - 1); + if (info->asCastle) + { + for(auto & elem : map->objects) + { + if(!elem) + continue; + + if(elem->ID==Obj::RANDOM_TOWN + && dynamic_cast(elem.get())->identifier == info->identifier) + { + randomizeObject(elem); //we have to randomize the castle first + faction = elem->subID; + break; + } + else if(elem->ID==Obj::TOWN + && dynamic_cast(elem.get())->identifier == info->identifier) + { + faction = elem->subID; + break; + } + } + } + else + { + while(!(info->castles[0]&(1<7) && (info->castles[1]&(1<<(faction-8)))) + break; + faction = rand.nextInt(GameConstants::F_NUMBER - 1); + } + } + } + else // castle alignment fixed + faction = obj->subID; + + int level; + + //if level set to range + if (auto info = dynamic_cast(dwl->info)) + { + level = rand.nextInt(info->minLevel, info->maxLevel); + } + else // fixed level + { + level = obj->subID; + } + + delete dwl->info; + dwl->info = nullptr; + + std::pair result(Obj::NO_OBJ, -1); + CreatureID cid = VLC->townh->factions[faction]->town->creatures[level][0]; + + //NOTE: this will pick last dwelling with this creature (Mantis #900) + //check for block map equality is better but more complex solution + auto testID = [&](Obj primaryID) -> void + { + auto dwellingIDs = VLC->objtypeh->knownSubObjects(primaryID); + for (si32 entry : dwellingIDs) + { + auto handler = dynamic_cast(VLC->objtypeh->getHandlerFor(primaryID, entry).get()); + + if (handler->producesCreature(VLC->creh->creatures[cid])) + result = std::make_pair(primaryID, entry); + } + }; + + testID(Obj::CREATURE_GENERATOR1); + if (result.first == Obj::NO_OBJ) + testID(Obj::CREATURE_GENERATOR4); + + if (result.first == Obj::NO_OBJ) + { + logGlobal->errorStream() << "Error: failed to find dwelling for "<< VLC->townh->factions[faction]->name << " of level " << int(level); + result = std::make_pair(Obj::CREATURE_GENERATOR1, *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), rand)); + } + + return result; + } + } + return std::make_pair(Obj::NO_OBJ,-1); +} + +void CGameState::randomizeObject(CGObjectInstance *cur) +{ + std::pair ran = pickObject(cur); + if(ran.first == Obj::NO_OBJ || ran.second<0) //this is not a random object, or we couldn't find anything + { + if(cur->ID==Obj::TOWN) + cur->setType(cur->ID, cur->subID); // update def, if necessary + return; + } + else if(ran.first==Obj::HERO)//special code for hero + { + CGHeroInstance *h = dynamic_cast(cur); + cur->setType(ran.first, ran.second); + map->heroesOnMap.push_back(h); + return; + } + else if(ran.first==Obj::TOWN)//special code for town + { + CGTownInstance *t = dynamic_cast(cur); + cur->setType(ran.first, ran.second); + map->towns.push_back(t); + return; + } + cur->setType(ran.first, ran.second); +} + +int CGameState::getDate(Date::EDateType mode) const +{ + int temp; + switch (mode) + { + case Date::DAY: + return day; + case Date::DAY_OF_WEEK: //day of week + temp = (day)%7; // 1 - Monday, 7 - Sunday + return temp ? temp : 7; + case Date::WEEK: //current week + temp = ((day-1)/7)+1; + if (!(temp%4)) + return 4; + else + return (temp%4); + case Date::MONTH: //current month + return ((day-1)/28)+1; + case Date::DAY_OF_MONTH: //day of month + temp = (day)%28; + if (temp) + return temp; + else return 28; + } + return 0; +} + +CGameState::CGameState() +{ + gs = this; + mx = new boost::shared_mutex(); + applierGs = new CApplier; + registerTypesClientPacks1(*applierGs); + registerTypesClientPacks2(*applierGs); + //objCaller = new CObjectCallersHandler; + globalEffects.setDescription("Global effects"); + globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS); +} + +CGameState::~CGameState() +{ + //delete mx;//TODO: crash on Linux (mutex must be unlocked before destruction) + map.dellNull(); + curB.dellNull(); + //delete scenarioOps; //TODO: fix for loading ind delete + //delete initialOpts; + delete applierGs; + //delete objCaller; + + for(auto ptr : hpool.heroesPool) // clean hero pool + ptr.second.dellNull(); +} + +BattleInfo * CGameState::setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance * heroes[2], bool creatureBank, const CGTownInstance *town) +{ + const TerrainTile &t = map->getTile(tile); + ETerrainType terrain = t.terType; + if(t.isCoastal() && !t.isWater()) + terrain = ETerrainType::SAND; + + BFieldType terType = battleGetBattlefieldType(tile); + if (heroes[0] && heroes[0]->boat && heroes[1] && heroes[1]->boat) + terType = BFieldType::SHIP_TO_SHIP; + return BattleInfo::setupBattle(tile, terrain, terType, armies, heroes, creatureBank, town); +} + +void CGameState::init(StartInfo * si) +{ + logGlobal->infoStream() << "\tUsing random seed: "<< si->seedToBeUsed; + rand.setSeed(si->seedToBeUsed); + scenarioOps = CMemorySerializer::deepCopy(*si).release(); + initialOpts = CMemorySerializer::deepCopy(*si).release(); + si = nullptr; + + switch(scenarioOps->mode) + { + case StartInfo::NEW_GAME: + initNewGame(); + break; + case StartInfo::CAMPAIGN: + initCampaign(); + break; + case StartInfo::DUEL: + initDuel(); + return; + default: + logGlobal->errorStream() << "Wrong mode: " << (int)scenarioOps->mode; + return; + } + VLC->arth->initAllowedArtifactsList(map->allowedArtifact); + logGlobal->infoStream() << "Map loaded!"; + + checkMapChecksum(); + + day = 0; + + logGlobal->debugStream() << "Initialization:"; + + initPlayerStates(); + placeCampaignHeroes(); + initGrailPosition(); + initRandomFactionsForPlayers(); + randomizeMapObjects(); + placeStartingHeroes(); + initStartingResources(); + initHeroes(); + initStartingBonus(); + initTowns(); + initMapObjects(); + buildBonusSystemTree(); + initVisitingAndGarrisonedHeroes(); + initFogOfWar(); + + logGlobal->debugStream() << "\tChecking objectives"; + map->checkForObjectives(); //needs to be run when all objects are properly placed + + auto seedAfterInit = rand.nextInt(); + logGlobal->infoStream() << "Seed after init is " << seedAfterInit << " (before was " << scenarioOps->seedToBeUsed << ")"; + if(scenarioOps->seedPostInit > 0) + { + //RNG must be in the same state on all machines when initialization is done (otherwise we have desync) + assert(scenarioOps->seedPostInit == seedAfterInit); + } + else + { + scenarioOps->seedPostInit = seedAfterInit; //store the post init "seed" + } +} + +void CGameState::initNewGame() +{ + if(scenarioOps->createRandomMap()) + { + logGlobal->infoStream() << "Create random map."; + CStopWatch sw; + + // Gen map + CMapGenerator mapGenerator(scenarioOps->mapGenOptions, scenarioOps->seedToBeUsed); + map = mapGenerator.generate().release(); + + // Update starting options + for(int i = 0; i < map->players.size(); ++i) + { + const auto & playerInfo = map->players[i]; + if(playerInfo.canAnyonePlay()) + { + PlayerSettings & playerSettings = scenarioOps->playerInfos[PlayerColor(i)]; + playerSettings.compOnly = !playerInfo.canHumanPlay; + playerSettings.team = playerInfo.team; + playerSettings.castle = playerInfo.defaultCastle(); + if(playerSettings.playerID == PlayerSettings::PLAYER_AI && playerSettings.name.empty()) + { + playerSettings.name = VLC->generaltexth->allTexts[468]; + } + playerSettings.color = PlayerColor(i); + } + else + { + scenarioOps->playerInfos.erase(PlayerColor(i)); + } + } + + logGlobal->infoStream() << boost::format("Generated random map in %i ms.") % sw.getDiff(); + } + else + { + logGlobal->infoStream() << "Open map file: " << scenarioOps->mapname; + map = CMapService::loadMap(scenarioOps->mapname).release(); + } +} + +void CGameState::initCampaign() +{ + logGlobal->infoStream() << "Open campaign map file: " << scenarioOps->campState->currentMap; + auto campaign = scenarioOps->campState; + assert(vstd::contains(campaign->camp->mapPieces, *scenarioOps->campState->currentMap)); + + std::string scenarioName = scenarioOps->mapname.substr(0, scenarioOps->mapname.find('.')); + boost::to_lower(scenarioName); + scenarioName += ':' + boost::lexical_cast(*campaign->currentMap); + + std::string & mapContent = campaign->camp->mapPieces[*campaign->currentMap]; + auto buffer = reinterpret_cast(mapContent.data()); + map = CMapService::loadMap(buffer, mapContent.size(), scenarioName).release(); +} + +void CGameState::initDuel() +{ + DuelParameters dp; + try //CLoadFile likes throwing + { + if(boost::algorithm::ends_with(scenarioOps->mapname, ".json")) + { + logGlobal->infoStream() << "Loading duel settings from JSON file: " << scenarioOps->mapname; + dp = DuelParameters::fromJSON(scenarioOps->mapname); + logGlobal->infoStream() << "JSON file has been successfully read!"; + } + else + { + CLoadFile lf(scenarioOps->mapname); + lf >> dp; + } + } + catch(...) + { + logGlobal->errorStream() << "Cannot load duel settings from " << scenarioOps->mapname; + throw; + } + + const CArmedInstance *armies[2] = {nullptr}; + const CGHeroInstance *heroes[2] = {nullptr}; + CGTownInstance *town = nullptr; + + for(int i = 0; i < 2; i++) + { + CArmedInstance *obj = nullptr; + if(dp.sides[i].heroId >= 0) + { + const DuelParameters::SideSettings &ss = dp.sides[i]; + auto h = new CGHeroInstance(); + armies[i] = heroes[i] = h; + obj = h; + h->subID = ss.heroId; + for(int i = 0; i < ss.heroPrimSkills.size(); i++) + h->pushPrimSkill(static_cast(i), ss.heroPrimSkills[i]); + + if(!ss.spells.empty()) + { + h->putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0)); + boost::copy(ss.spells, std::inserter(h->spells, h->spells.begin())); + } + + for(auto &parka : ss.artifacts) + { + h->putArtifact(ArtifactPosition(parka.first), parka.second); + } + + typedef const std::pair &TSecSKill; + for(TSecSKill secSkill : ss.heroSecSkills) + h->setSecSkillLevel(SecondarySkill(secSkill.first), secSkill.second, 1); + + h->initHero(HeroTypeID(h->subID)); + obj->initObj(); + } + else + { + auto c = new CGCreature(); + armies[i] = obj = c; + //c->subID = 34; + } + + obj->setOwner(PlayerColor(i)); + + for(int j = 0; j < ARRAY_COUNT(dp.sides[i].stacks); j++) + { + CreatureID cre = dp.sides[i].stacks[j].type; + TQuantity count = dp.sides[i].stacks[j].count; + if(count || obj->hasStackAtSlot(SlotID(j))) + obj->setCreature(SlotID(j), cre, count); + } + + for(const DuelParameters::CusomCreature &cc : dp.creatures) + { + CCreature *c = VLC->creh->creatures[cc.id]; + if(cc.attack >= 0) + c->getBonusLocalFirst(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK))->val = cc.attack; + if(cc.defense >= 0) + c->getBonusLocalFirst(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE))->val = cc.defense; + if(cc.speed >= 0) + c->getBonusLocalFirst(Selector::type(Bonus::STACKS_SPEED))->val = cc.speed; + if(cc.HP >= 0) + c->getBonusLocalFirst(Selector::type(Bonus::STACK_HEALTH))->val = cc.HP; + if(cc.dmg >= 0) + { + c->getBonusLocalFirst(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1))->val = cc.dmg; + c->getBonusLocalFirst(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2))->val = cc.dmg; + } + if(cc.shoots >= 0) + c->getBonusLocalFirst(Selector::type(Bonus::SHOTS))->val = cc.shoots; + } + } + + curB = BattleInfo::setupBattle(int3(), dp.terType, dp.bfieldType, armies, heroes, false, town); + curB->obstacles = dp.obstacles; + curB->localInit(); +} + +void CGameState::checkMapChecksum() +{ + logGlobal->infoStream() << "\tOur checksum for the map: "<< map->checksum; + if(scenarioOps->mapfileChecksum) + { + logGlobal->infoStream() << "\tServer checksum for " << scenarioOps->mapname <<": "<< scenarioOps->mapfileChecksum; + if(map->checksum != scenarioOps->mapfileChecksum) + { + logGlobal->errorStream() << "Wrong map checksum!!!"; + throw std::runtime_error("Wrong checksum"); + } + } + else + { + scenarioOps->mapfileChecksum = map->checksum; + } +} + +void CGameState::initGrailPosition() +{ + logGlobal->debugStream() << "\tPicking grail position"; + //pick grail location + if(map->grailPos.x < 0 || map->grailRadious) //grail not set or set within a radius around some place + { + if(!map->grailRadious) //radius not given -> anywhere on map + map->grailRadious = map->width * 2; + + std::vector allowedPos; + static const int BORDER_WIDTH = 9; // grail must be at least 9 tiles away from border + + // add all not blocked tiles in range + for (int i = BORDER_WIDTH; i < map->width - BORDER_WIDTH ; i++) + { + for (int j = BORDER_WIDTH; j < map->height - BORDER_WIDTH; j++) + { + for (int k = 0; k < (map->twoLevel ? 2 : 1); k++) + { + const TerrainTile &t = map->getTile(int3(i, j, k)); + if(!t.blocked + && !t.visitable + && t.terType != ETerrainType::WATER + && t.terType != ETerrainType::ROCK + && map->grailPos.dist2dSQ(int3(i, j, k)) <= (map->grailRadious * map->grailRadious)) + allowedPos.push_back(int3(i,j,k)); + } + } + } + + //remove tiles with holes + for(auto & elem : map->objects) + if(elem && elem->ID == Obj::HOLE) + allowedPos -= elem->pos; + + if(!allowedPos.empty()) + { + map->grailPos = *RandomGeneratorUtil::nextItem(allowedPos, rand); + } + else + { + logGlobal->warnStream() << "Warning: Grail cannot be placed, no appropriate tile found!"; + } + } +} + +void CGameState::initRandomFactionsForPlayers() +{ + logGlobal->debugStream() << "\tPicking random factions for players"; + for(auto & elem : scenarioOps->playerInfos) + { + if(elem.second.castle==-1) + { + auto randomID = rand.nextInt(map->players[elem.first.getNum()].allowedFactions.size() - 1); + auto iter = map->players[elem.first.getNum()].allowedFactions.begin(); + std::advance(iter, randomID); + + elem.second.castle = *iter; + } + } +} + +void CGameState::randomizeMapObjects() +{ + logGlobal->debugStream() << "\tRandomizing objects"; + for(CGObjectInstance *obj : map->objects) + { + if(!obj) continue; + + randomizeObject(obj); + obj->hoverName = VLC->objtypeh->getObjectName(obj->ID); + + //handle Favouring Winds - mark tiles under it + if(obj->ID == Obj::FAVORABLE_WINDS) + { + for (int i = 0; i < obj->getWidth() ; i++) + { + for (int j = 0; j < obj->getHeight() ; j++) + { + int3 pos = obj->pos - int3(i,j,0); + if(map->isInTheMap(pos)) map->getTile(pos).extTileFlags |= 128; + } + } + } + } +} + +void CGameState::initPlayerStates() +{ + logGlobal->debugStream() << "\tCreating player entries in gs"; + for(auto & elem : scenarioOps->playerInfos) + { + std::pair ins(elem.first,PlayerState()); + ins.second.color=ins.first; + ins.second.human = elem.second.playerID; + ins.second.team = map->players[ins.first.getNum()].team; + teams[ins.second.team].id = ins.second.team;//init team + teams[ins.second.team].players.insert(ins.first);//add player to team + players.insert(ins); + } +} + +void CGameState::placeCampaignHeroes() +{ + if (scenarioOps->campState) + { + // place bonus hero + auto campaignBonus = scenarioOps->campState->getBonusForCurrentMap(); + bool campaignGiveHero = campaignBonus && campaignBonus.get().type == CScenarioTravel::STravelBonus::HERO; + + if(campaignGiveHero) + { + auto playerColor = PlayerColor(campaignBonus->info1); + auto it = scenarioOps->playerInfos.find(playerColor); + if(it != scenarioOps->playerInfos.end()) + { + auto heroTypeId = campaignBonus->info2; + if(heroTypeId == 0xffff) // random bonus hero + { + heroTypeId = pickUnusedHeroTypeRandomly(playerColor); + } + + placeStartingHero(playerColor, HeroTypeID(heroTypeId), map->players[playerColor.getNum()].posOfMainTown); + } + } + + // replace heroes placeholders + auto crossoverHeroes = getCrossoverHeroesFromPreviousScenarios(); + + if(!crossoverHeroes.heroesFromAnyPreviousScenarios.empty()) + { + logGlobal->debugStream() << "\tGenerate list of hero placeholders"; + auto campaignHeroReplacements = generateCampaignHeroesToReplace(crossoverHeroes); + + logGlobal->debugStream() << "\tPrepare crossover heroes"; + prepareCrossoverHeroes(campaignHeroReplacements, scenarioOps->campState->getCurrentScenario().travelOptions); + + // remove same heroes on the map which will be added through crossover heroes + // INFO: we will remove heroes because later it may be possible that the API doesn't allow having heroes + // with the same hero type id + std::vector removedHeroes; + + for(auto & campaignHeroReplacement : campaignHeroReplacements) + { + auto hero = getUsedHero(HeroTypeID(campaignHeroReplacement.hero->subID)); + if(hero) + { + removedHeroes.push_back(hero); + map->heroesOnMap -= hero; + map->objects[hero->id.getNum()] = nullptr; + map->removeBlockVisTiles(hero, true); + } + } + + logGlobal->debugStream() << "\tReplace placeholders with heroes"; + replaceHeroesPlaceholders(campaignHeroReplacements); + + // remove hero placeholders on map + for(auto obj : map->objects) + { + if(obj && obj->ID == Obj::HERO_PLACEHOLDER) + { + auto heroPlaceholder = dynamic_cast(obj.get()); + map->removeBlockVisTiles(heroPlaceholder, true); + map->objects[heroPlaceholder->id.getNum()] = nullptr; + delete heroPlaceholder; + } + } + + // now add removed heroes again with unused type ID + for(auto hero : removedHeroes) + { + si32 heroTypeId = 0; + if(hero->ID == Obj::HERO) + { + heroTypeId = pickUnusedHeroTypeRandomly(hero->tempOwner); + } + else if(hero->ID == Obj::PRISON) + { + auto unusedHeroTypeIds = getUnusedAllowedHeroes(); + if(!unusedHeroTypeIds.empty()) + { + heroTypeId = (*RandomGeneratorUtil::nextItem(unusedHeroTypeIds, rand)).getNum(); + } + else + { + logGlobal->errorStream() << "No free hero type ID found to replace prison."; + assert(0); + } + } + else + { + assert(0); // should not happen + } + + hero->subID = heroTypeId; + hero->portrait = hero->subID; + map->getEditManager()->insertObject(hero, hero->pos); + } + } + } +} + +void CGameState::placeStartingHero(PlayerColor playerColor, HeroTypeID heroTypeId, int3 townPos) +{ + townPos.x += 1; + + CGHeroInstance * hero = static_cast(createObject(Obj::HERO, heroTypeId.getNum(), townPos, playerColor)); + map->getEditManager()->insertObject(hero, townPos); +} + +CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenarios() const +{ + CrossoverHeroesList crossoverHeroes; + + auto campaignState = scenarioOps->campState; + auto bonus = campaignState->getBonusForCurrentMap(); + if (bonus && bonus->type == CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO) + { + crossoverHeroes.heroesFromAnyPreviousScenarios = crossoverHeroes.heroesFromPreviousScenario = campaignState->camp->scenarios[bonus->info2].crossoverHeroes; + } + else + { + if(!campaignState->mapsConquered.empty()) + { + crossoverHeroes.heroesFromPreviousScenario = campaignState->camp->scenarios[campaignState->mapsConquered.back()].crossoverHeroes; + + for(auto mapNr : campaignState->mapsConquered) + { + // create a list of deleted heroes + auto & scenario = campaignState->camp->scenarios[mapNr]; + auto lostCrossoverHeroes = scenario.getLostCrossoverHeroes(); + + // remove heroes which didn't reached the end of the scenario, but were available at the start + for(auto hero : lostCrossoverHeroes) + { + crossoverHeroes.heroesFromAnyPreviousScenarios.erase(range::remove_if(crossoverHeroes.heroesFromAnyPreviousScenarios, + CGObjectInstanceBySubIdFinder(hero)), crossoverHeroes.heroesFromAnyPreviousScenarios.end()); + } + + // now add heroes which completed the scenario + for(auto hero : scenario.crossoverHeroes) + { + // add new heroes and replace old heroes with newer ones + auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, CGObjectInstanceBySubIdFinder(hero)); + if (it != crossoverHeroes.heroesFromAnyPreviousScenarios.end()) + { + // replace old hero with newer one + crossoverHeroes.heroesFromAnyPreviousScenarios[it - crossoverHeroes.heroesFromAnyPreviousScenarios.begin()] = hero; + } + else + { + // add new hero + crossoverHeroes.heroesFromAnyPreviousScenarios.push_back(hero); + } + } + } + } + } + + return crossoverHeroes; +} + +void CGameState::prepareCrossoverHeroes(std::vector & campaignHeroReplacements, const CScenarioTravel & travelOptions) const +{ + // create heroes list for convenience iterating + std::vector crossoverHeroes; + for(auto & campaignHeroReplacement : campaignHeroReplacements) + { + crossoverHeroes.push_back(campaignHeroReplacement.hero); + } + + // TODO replace magic numbers with named constants + // TODO this logic (what should be kept) should be part of CScenarioTravel and be exposed via some clean set of methods + if(!(travelOptions.whatHeroKeeps & 1)) + { + //trimming experience + for(CGHeroInstance * cgh : crossoverHeroes) + { + cgh->initExp(); + } + } + + if(!(travelOptions.whatHeroKeeps & 2)) + { + //trimming prim skills + for(CGHeroInstance * cgh : crossoverHeroes) + { + for(int g=0; ggetBonusLocalFirst(sel)->val = cgh->type->heroClass->primarySkillInitial[g]; + } + } + } + + if(!(travelOptions.whatHeroKeeps & 4)) + { + //trimming sec skills + for(CGHeroInstance * cgh : crossoverHeroes) + { + cgh->secSkills = cgh->type->secSkillsInit; + cgh->recreateSecondarySkillsBonuses(); + } + } + + if(!(travelOptions.whatHeroKeeps & 8)) + { + for(CGHeroInstance * cgh : crossoverHeroes) + { + // Trimming spells + cgh->spells.clear(); + + // Spellbook will also be removed + if (cgh->hasSpellbook()) + ArtifactLocation(cgh, ArtifactPosition(ArtifactPosition::SPELLBOOK)).removeArtifact(); + } + } + + if(!(travelOptions.whatHeroKeeps & 16)) + { + //trimming artifacts + for(CGHeroInstance * hero : crossoverHeroes) + { + size_t totalArts = GameConstants::BACKPACK_START + hero->artifactsInBackpack.size(); + for (size_t i = 0; i < totalArts; i++ ) + { + auto artifactPosition = ArtifactPosition(i); + if(artifactPosition == ArtifactPosition::SPELLBOOK) continue; // do not handle spellbook this way + + const ArtSlotInfo *info = hero->getSlot(artifactPosition); + if(!info) + continue; + + // TODO: why would there be nullptr artifacts? + const CArtifactInstance *art = info->artifact; + if(!art) + continue; + + int id = art->artType->id; + assert( 8*18 > id );//number of arts that fits into h3m format + bool takeable = travelOptions.artifsKeptByHero[id / 8] & ( 1 << (id%8) ); + + ArtifactLocation al(hero, artifactPosition); + if(!takeable && !al.getSlot()->locked) //don't try removing locked artifacts -> it crashes #1719 + al.removeArtifact(); + } + } + } + + //trimming creatures + for(CGHeroInstance * cgh : crossoverHeroes) + { + auto shouldSlotBeErased = [&](const std::pair & j) -> bool + { + CreatureID::ECreatureID crid = j.second->getCreatureID().toEnum(); + return !(travelOptions.monstersKeptByHero[crid / 8] & (1 << (crid % 8))); + }; + + auto stacksCopy = cgh->stacks; //copy of the map, so we can iterate iover it and remove stacks + for(auto &slotPair : stacksCopy) + if(shouldSlotBeErased(slotPair)) + cgh->eraseStack(slotPair.first); + } + + // Removing short-term bonuses + for(CGHeroInstance * cgh : crossoverHeroes) + { + cgh->popBonuses(Selector::durationType(Bonus::ONE_DAY)); + cgh->popBonuses(Selector::durationType(Bonus::ONE_WEEK)); + cgh->popBonuses(Selector::durationType(Bonus::N_TURNS)); + cgh->popBonuses(Selector::durationType(Bonus::N_DAYS)); + cgh->popBonuses(Selector::durationType(Bonus::ONE_BATTLE)); + } + +} + +void CGameState::placeStartingHeroes() +{ + logGlobal->debugStream() << "\tGiving starting hero"; + + for(auto & playerSettingPair : scenarioOps->playerInfos) + { + auto playerColor = playerSettingPair.first; + auto & playerInfo = map->players[playerColor.getNum()]; + if(playerInfo.generateHeroAtMainTown && playerInfo.hasMainTown) + { + // Do not place a starting hero if the hero was already placed due to a campaign bonus + if(scenarioOps->campState) + { + if(auto campaignBonus = scenarioOps->campState->getBonusForCurrentMap()) + { + if(campaignBonus->type == CScenarioTravel::STravelBonus::HERO && playerColor == PlayerColor(campaignBonus->info1)) continue; + } + } + + int heroTypeId = pickNextHeroType(playerColor); + if(playerSettingPair.second.hero == -1) playerSettingPair.second.hero = heroTypeId; + + placeStartingHero(playerColor, HeroTypeID(heroTypeId), playerInfo.posOfMainTown); + } + } +} + +void CGameState::initStartingResources() +{ + logGlobal->debugStream() << "\tSetting up resources"; + const JsonNode config(ResourceID("config/startres.json")); + const JsonVector &vector = config["difficulty"].Vector(); + const JsonNode &level = vector[scenarioOps->difficulty]; + + TResources startresAI(level["ai"]); + TResources startresHuman(level["human"]); + + for (auto & elem : players) + { + PlayerState &p = elem.second; + + if (p.human) + p.resources = startresHuman; + else + p.resources = startresAI; + } + + auto getHumanPlayerInfo = [&]() -> std::vector + { + std::vector ret; + for(auto it = scenarioOps->playerInfos.cbegin(); + it != scenarioOps->playerInfos.cend(); ++it) + { + if(it->second.playerID != PlayerSettings::PLAYER_AI) + ret.push_back(&it->second); + } + + return ret; + }; + + //give start resource bonus in case of campaign + if (scenarioOps->mode == StartInfo::CAMPAIGN) + { + auto chosenBonus = scenarioOps->campState->getBonusForCurrentMap(); + if(chosenBonus && chosenBonus->type == CScenarioTravel::STravelBonus::RESOURCE) + { + std::vector people = getHumanPlayerInfo(); //players we will give resource bonus + for(const PlayerSettings *ps : people) + { + std::vector res; //resources we will give + switch (chosenBonus->info1) + { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: + res.push_back(chosenBonus->info1); + break; + case 0xFD: //wood+ore + res.push_back(Res::WOOD); res.push_back(Res::ORE); + break; + case 0xFE: //rare + res.push_back(Res::MERCURY); res.push_back(Res::SULFUR); res.push_back(Res::CRYSTAL); res.push_back(Res::GEMS); + break; + default: + assert(0); + break; + } + //increasing resource quantity + for (auto & re : res) + { + players[ps->color].resources[re] += chosenBonus->info2; + } + } + } + } +} + +void CGameState::initHeroes() +{ + for(auto hero : map->heroesOnMap) //heroes instances initialization + { + if (hero->getOwner() == PlayerColor::UNFLAGGABLE) + { + logGlobal->warnStream() << "Warning - hero with uninitialized owner!"; + continue; + } + + hero->initHero(); + getPlayer(hero->getOwner())->heroes.push_back(hero); + map->allHeroes[hero->type->ID.getNum()] = hero; + } + + for(auto obj : map->objects) //prisons + { + if(obj && obj->ID == Obj::PRISON) + map->allHeroes[obj->subID] = dynamic_cast(obj.get()); + } + + std::set heroesToCreate = getUnusedAllowedHeroes(); //ids of heroes to be created and put into the pool + for(auto ph : map->predefinedHeroes) + { + if(!vstd::contains(heroesToCreate, HeroTypeID(ph->subID))) + continue; + ph->initHero(); + hpool.heroesPool[ph->subID] = ph; + hpool.pavailable[ph->subID] = 0xff; + heroesToCreate.erase(ph->type->ID); + + map->allHeroes[ph->subID] = ph; + } + + for(HeroTypeID htype : heroesToCreate) //all not used allowed heroes go with default state into the pool + { + auto vhi = new CGHeroInstance(); + vhi->initHero(htype); + + int typeID = htype.getNum(); + map->allHeroes[typeID] = vhi; + hpool.heroesPool[typeID] = vhi; + hpool.pavailable[typeID] = 0xff; + } + + for(auto & elem : map->disposedHeroes) + { + hpool.pavailable[elem.heroId] = elem.players; + } + + if (scenarioOps->mode == StartInfo::CAMPAIGN) //give campaign bonuses for specific / best hero + { + auto chosenBonus = scenarioOps->campState->getBonusForCurrentMap(); + if (chosenBonus && chosenBonus->isBonusForHero() && chosenBonus->info1 != 0xFFFE) //exclude generated heroes + { + //find human player + PlayerColor humanPlayer=PlayerColor::NEUTRAL; + for (auto & elem : players) + { + if(elem.second.human) + { + humanPlayer = elem.first; + break; + } + } + assert(humanPlayer != PlayerColor::NEUTRAL); + + std::vector > & heroes = players[humanPlayer].heroes; + + if (chosenBonus->info1 == 0xFFFD) //most powerful + { + int maxB = -1; + for (int b=0; bgetTotalStrength() > heroes[maxB]->getTotalStrength()) + { + maxB = b; + } + } + if(maxB < 0) + logGlobal->warnStream() << "Warning - cannot give bonus to hero cause there are no heroes!"; + else + giveCampaignBonusToHero(heroes[maxB]); + } + else //specific hero + { + for (auto & heroe : heroes) + { + if (heroe->subID == chosenBonus->info1) + { + giveCampaignBonusToHero(heroe); + break; + } + } + } + } + } +} + +void CGameState::giveCampaignBonusToHero(CGHeroInstance * hero) +{ + const boost::optional & curBonus = scenarioOps->campState->getBonusForCurrentMap(); + if(!curBonus) + return; + + if(curBonus->isBonusForHero()) + { + //apply bonus + switch (curBonus->type) + { + case CScenarioTravel::STravelBonus::SPELL: + hero->spells.insert(SpellID(curBonus->info2)); + break; + case CScenarioTravel::STravelBonus::MONSTER: + { + for(int i=0; islotEmpty(SlotID(i))) + { + hero->addToSlot(SlotID(i), CreatureID(curBonus->info2), curBonus->info3); + break; + } + } + } + break; + case CScenarioTravel::STravelBonus::ARTIFACT: + gs->giveHeroArtifact(hero, static_cast(curBonus->info2)); + break; + case CScenarioTravel::STravelBonus::SPELL_SCROLL: + { + CArtifactInstance * scroll = CArtifactInstance::createScroll(SpellID(curBonus->info2).toSpell()); + scroll->putAt(ArtifactLocation(hero, scroll->firstAvailableSlot(hero))); + } + break; + case CScenarioTravel::STravelBonus::PRIMARY_SKILL: + { + const ui8* ptr = reinterpret_cast(&curBonus->info2); + for (int g=0; gcampState->currentMap, g); + hero->addNewBonus(bb); + } + } + break; + case CScenarioTravel::STravelBonus::SECONDARY_SKILL: + hero->setSecSkillLevel(SecondarySkill(curBonus->info2), curBonus->info3, true); + break; + } + } +} + +void CGameState::initFogOfWar() +{ + logGlobal->debugStream() << "\tFog of war"; //FIXME: should be initialized after all bonuses are set + for(auto & elem : teams) + { + elem.second.fogOfWarMap.resize(map->width); + for(int g=0; gwidth; ++g) + elem.second.fogOfWarMap[g].resize(map->height); + + for(int g=-0; gwidth; ++g) + for(int h=0; hheight; ++h) + elem.second.fogOfWarMap[g][h].resize(map->twoLevel ? 2 : 1, 0); + + for(int g=0; gwidth; ++g) + for(int h=0; hheight; ++h) + for(int v = 0; v < (map->twoLevel ? 2 : 1); ++v) + elem.second.fogOfWarMap[g][h][v] = 0; + + for(CGObjectInstance *obj : map->objects) + { + if(!obj || !vstd::contains(elem.second.players, obj->tempOwner)) continue; //not a flagged object + + std::unordered_set tiles; + obj->getSightTiles(tiles); + for(int3 tile : tiles) + { + elem.second.fogOfWarMap[tile.x][tile.y][tile.z] = 1; + } + } + } +} + +void CGameState::initStartingBonus() +{ + logGlobal->debugStream() << "\tStarting bonuses"; + for(auto & elem : players) + { + //starting bonus + if(scenarioOps->playerInfos[elem.first].bonus==PlayerSettings::RANDOM) + scenarioOps->playerInfos[elem.first].bonus = static_cast(rand.nextInt(2)); + switch(scenarioOps->playerInfos[elem.first].bonus) + { + case PlayerSettings::GOLD: + elem.second.resources[Res::GOLD] += rand.nextInt(500, 1000); + break; + case PlayerSettings::RESOURCE: + { + int res = VLC->townh->factions[scenarioOps->playerInfos[elem.first].castle]->town->primaryRes; + if(res == Res::WOOD_AND_ORE) + { + elem.second.resources[Res::WOOD] += rand.nextInt(5, 10); + elem.second.resources[Res::ORE] += rand.nextInt(5, 10); + } + else + { + elem.second.resources[res] += rand.nextInt(3, 6); + } + break; + } + case PlayerSettings::ARTIFACT: + { + if(!elem.second.heroes.size()) + { + logGlobal->debugStream() << "Cannot give starting artifact - no heroes!"; + break; + } + CArtifact *toGive; + toGive = VLC->arth->artifacts[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE)]; + + CGHeroInstance *hero = elem.second.heroes[0]; + giveHeroArtifact(hero, toGive->id); + } + break; + } + } +} + +void CGameState::initTowns() +{ + logGlobal->debugStream() << "\tTowns"; + + //campaign bonuses for towns + if (scenarioOps->mode == StartInfo::CAMPAIGN) + { + auto chosenBonus = scenarioOps->campState->getBonusForCurrentMap(); + + if (chosenBonus && chosenBonus->type == CScenarioTravel::STravelBonus::BUILDING) + { + for (int g=0; gtowns.size(); ++g) + { + PlayerState * owner = getPlayer(map->towns[g]->getOwner()); + if (owner) + { + PlayerInfo & pi = map->players[owner->color.getNum()]; + + if (owner->human && //human-owned + map->towns[g]->pos == pi.posOfMainTown + int3(2, 0, 0)) + { + map->towns[g]->builtBuildings.insert( + CBuildingHandler::campToERMU(chosenBonus->info1, map->towns[g]->subID, map->towns[g]->builtBuildings)); + break; + } + } + } + } + } + + CGTownInstance::universitySkills.clear(); + for ( int i=0; i<4; i++) + CGTownInstance::universitySkills.push_back(14+i);//skills for university + + for (auto & elem : map->towns) + { + CGTownInstance * vti =(elem); + if(!vti->town) + { + vti->town = VLC->townh->factions[vti->subID]->town; + } + if(vti->name.empty()) + { + vti->name = *RandomGeneratorUtil::nextItem(vti->town->names, rand); + } + + //init buildings + if(vstd::contains(vti->builtBuildings, BuildingID::DEFAULT)) //give standard set of buildings + { + vti->builtBuildings.erase(BuildingID::DEFAULT); + vti->builtBuildings.insert(BuildingID::VILLAGE_HALL); + vti->builtBuildings.insert(BuildingID::TAVERN); + vti->builtBuildings.insert(BuildingID::DWELL_FIRST); + if(rand.nextInt(1) == 1) + { + vti->builtBuildings.insert(BuildingID::DWELL_LVL_2); + } + } + + //#1444 - remove entries that don't have buildings defined (like some unused extra town hall buildings) + vstd::erase_if(vti->builtBuildings, [vti](BuildingID bid){ + return !vti->town->buildings.count(bid) || !vti->town->buildings.at(bid); }); + + if (vstd::contains(vti->builtBuildings, BuildingID::SHIPYARD) && vti->shipyardStatus()==IBoatGenerator::TILE_BLOCKED) + vti->builtBuildings.erase(BuildingID::SHIPYARD);//if we have harbor without water - erase it (this is H3 behaviour) + + //init hordes + for (int i = 0; ibuiltBuildings,(-31-i))) //if we have horde for this level + { + vti->builtBuildings.erase(BuildingID(-31-i));//remove old ID + if (vti->town->hordeLvl.at(0) == i)//if town first horde is this one + { + vti->builtBuildings.insert(BuildingID::HORDE_1);//add it + if (vstd::contains(vti->builtBuildings,(BuildingID::DWELL_UP_FIRST+i)))//if we have upgraded dwelling as well + vti->builtBuildings.insert(BuildingID::HORDE_1_UPGR);//add it as well + } + if (vti->town->hordeLvl.at(1) == i)//if town second horde is this one + { + vti->builtBuildings.insert(BuildingID::HORDE_2); + if (vstd::contains(vti->builtBuildings,(BuildingID::DWELL_UP_FIRST+i))) + vti->builtBuildings.insert(BuildingID::HORDE_2_UPGR); + } + } + + //Early check for #1444-like problems + for(auto building : vti->builtBuildings) + { + assert(vti->town->buildings.at(building) != nullptr); + UNUSED(building); + } + + //town events + for(CCastleEvent &ev : vti->events) + { + for (int i = 0; itown->hordeLvl.at(0) == i) + ev.buildings.insert(BuildingID::HORDE_1); + if (vti->town->hordeLvl.at(1) == i) + ev.buildings.insert(BuildingID::HORDE_2); + } + } + //init spells + logGlobal->debugStream() << "\t\tTown init spells"; + vti->spells.resize(GameConstants::SPELL_LEVELS); + + for(ui32 z=0; zobligatorySpells.size();z++) + { + CSpell *s = vti->obligatorySpells[z].toSpell(); + vti->spells[s->level-1].push_back(s->id); + vti->possibleSpells -= s->id; + } + logGlobal->debugStream() << "\t\tTown init spells2"; + while(vti->possibleSpells.size()) + { + ui32 total=0; + int sel = -1; + + for(ui32 ps=0;pspossibleSpells.size();ps++) + total += vti->possibleSpells[ps].toSpell()->getProbability(vti->subID); + + if (total == 0) // remaining spells have 0 probability + break; + + auto r = rand.nextInt(total - 1); + for(ui32 ps=0; pspossibleSpells.size();ps++) + { + r -= vti->possibleSpells[ps].toSpell()->getProbability(vti->subID); + if(r<0) + { + sel = ps; + break; + } + } + if(sel<0) + sel=0; + + CSpell *s = vti->possibleSpells[sel].toSpell(); + vti->spells[s->level-1].push_back(s->id); + vti->possibleSpells -= s->id; + } + vti->possibleSpells.clear(); + if(vti->getOwner() != PlayerColor::NEUTRAL) + getPlayer(vti->getOwner())->towns.push_back(vti); + logGlobal->debugStream() << "\t\tTown init spells3"; + + } +} + +void CGameState::initMapObjects() +{ + logGlobal->debugStream() << "\tObject initialization"; +// objCaller->preInit(); + for(CGObjectInstance *obj : map->objects) + { + if(obj) + obj->initObj(); + } + for(CGObjectInstance *obj : map->objects) + { + if(!obj) + continue; + + switch (obj->ID) + { + case Obj::QUEST_GUARD: + case Obj::SEER_HUT: + { + auto q = static_cast(obj); + assert (q); + q->setObjToKill(); + } + } + } + CGTeleport::postInit(); //pairing subterranean gates + + map->calculateGuardingGreaturePositions(); //calculate once again when all the guards are placed and initialized +} + +void CGameState::initVisitingAndGarrisonedHeroes() +{ + for(auto k=players.begin(); k!=players.end(); ++k) + { + if(k->first==PlayerColor::NEUTRAL) + continue; + + //init visiting and garrisoned heroes + for(CGHeroInstance *h : k->second.heroes) + { + for(CGTownInstance *t : k->second.towns) + { + int3 vistile = t->pos; vistile.x--; //tile next to the entrance + if(vistile == h->pos || h->pos==t->pos) + { + t->setVisitingHero(h); + if(h->pos == t->pos) //visiting hero placed in the editor has same pos as the town - we need to correct it + { + map->removeBlockVisTiles(h); + h->pos.x -= 1; + map->addBlockVisTiles(h); + } + break; + } + } + } + } +} + +BFieldType CGameState::battleGetBattlefieldType(int3 tile) +{ + if(tile==int3() && curB) + tile = curB->tile; + else if(tile==int3() && !curB) + return BFieldType::NONE; + + const TerrainTile &t = map->getTile(tile); + //fight in mine -> subterranean + if(dynamic_cast(t.visitableObjects.front())) + return BFieldType::SUBTERRANEAN; + + for(auto &obj : map->objects) + { + //look only for objects covering given tile + if( !obj || obj->pos.z != tile.z || !obj->coveringAt(tile.x, tile.y)) + continue; + + switch(obj->ID) + { + case Obj::CLOVER_FIELD: + return BFieldType::CLOVER_FIELD; + case Obj::CURSED_GROUND1: case Obj::CURSED_GROUND2: + return BFieldType::CURSED_GROUND; + case Obj::EVIL_FOG: + return BFieldType::EVIL_FOG; + case Obj::FAVORABLE_WINDS: + return BFieldType::FAVOURABLE_WINDS; + case Obj::FIERY_FIELDS: + return BFieldType::FIERY_FIELDS; + case Obj::HOLY_GROUNDS: + return BFieldType::HOLY_GROUND; + case Obj::LUCID_POOLS: + return BFieldType::LUCID_POOLS; + case Obj::MAGIC_CLOUDS: + return BFieldType::MAGIC_CLOUDS; + case Obj::MAGIC_PLAINS1: case Obj::MAGIC_PLAINS2: + return BFieldType::MAGIC_PLAINS; + case Obj::ROCKLANDS: + return BFieldType::ROCKLANDS; + } + } + + if(!t.isWater() && t.isCoastal()) + return BFieldType::SAND_SHORE; + + switch(t.terType) + { + case ETerrainType::DIRT: + return BFieldType(rand.nextInt(3, 5)); + case ETerrainType::SAND: + return BFieldType::SAND_MESAS; //TODO: coast support + case ETerrainType::GRASS: + return BFieldType(rand.nextInt(6, 7)); + case ETerrainType::SNOW: + return BFieldType(rand.nextInt(10, 11)); + case ETerrainType::SWAMP: + return BFieldType::SWAMP_TREES; + case ETerrainType::ROUGH: + return BFieldType::ROUGH; + case ETerrainType::SUBTERRANEAN: + return BFieldType::SUBTERRANEAN; + case ETerrainType::LAVA: + return BFieldType::LAVA; + case ETerrainType::WATER: + return BFieldType::SHIP; + case ETerrainType::ROCK: + return BFieldType::ROCKLANDS; + default: + return BFieldType::NONE; + } +} + +UpgradeInfo CGameState::getUpgradeInfo(const CStackInstance &stack) +{ + UpgradeInfo ret; + const CCreature *base = stack.type; + + const CGHeroInstance *h = stack.armyObj->ID == Obj::HERO ? static_cast(stack.armyObj) : nullptr; + const CGTownInstance *t = nullptr; + + if(stack.armyObj->ID == Obj::TOWN) + t = static_cast(stack.armyObj); + else if(h) + { //hero specialty + TBonusListPtr lista = h->getBonuses(Selector::typeSubtype(Bonus::SPECIAL_UPGRADE, base->idNumber)); + for(const Bonus *it : *lista) + { + auto nid = CreatureID(it->additionalInfo); + if (nid != base->idNumber) //in very specific case the upgrade is available by default (?) + { + ret.newID.push_back(nid); + ret.cost.push_back(VLC->creh->creatures[nid]->cost - base->cost); + } + } + t = h->visitedTown; + } + if(t) + { + for(const CGTownInstance::TCreaturesSet::value_type & dwelling : t->creatures) + { + if (vstd::contains(dwelling.second, base->idNumber)) //Dwelling with our creature + { + for(auto upgrID : dwelling.second) + { + if(vstd::contains(base->upgrades, upgrID)) //possible upgrade + { + ret.newID.push_back(upgrID); + ret.cost.push_back(VLC->creh->creatures[upgrID]->cost - base->cost); + } + } + } + } + } + + //hero is visiting Hill Fort + if(h && map->getTile(h->visitablePos()).visitableObjects.front()->ID == Obj::HILL_FORT) + { + static const int costModifiers[] = {0, 25, 50, 75, 100}; //we get cheaper upgrades depending on level + const int costModifier = costModifiers[std::min(std::max((int)base->level - 1, 0), ARRAY_COUNT(costModifiers) - 1)]; + + for(auto nid : base->upgrades) + { + ret.newID.push_back(nid); + ret.cost.push_back((VLC->creh->creatures[nid]->cost - base->cost) * costModifier / 100); + } + } + + if(ret.newID.size()) + ret.oldID = base->idNumber; + + for (Res::ResourceSet &cost : ret.cost) + cost.positive(); //upgrade cost can't be negative, ignore missing resources + + return ret; +} + +PlayerRelations::PlayerRelations CGameState::getPlayerRelations( PlayerColor color1, PlayerColor color2 ) +{ + if ( color1 == color2 ) + return PlayerRelations::SAME_PLAYER; + if(color1 == PlayerColor::NEUTRAL || color2 == PlayerColor::NEUTRAL) //neutral player has no friends + return PlayerRelations::ENEMIES; + + const TeamState * ts = getPlayerTeam(color1); + if (ts && vstd::contains(ts->players, color2)) + return PlayerRelations::ALLIES; + return PlayerRelations::ENEMIES; +} + +void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vector &vec, const boost::logic::tribool &onLand, bool limitCoastSailing) +{ + static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), + int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) }; + + //vec.reserve(8); //optimization + for (auto & dir : dirs) + { + const int3 hlp = tile + dir; + if(!map->isInTheMap(hlp)) + continue; + + const TerrainTile &hlpt = map->getTile(hlp); + +// //we cannot visit things from blocked tiles +// if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE) +// { +// continue; +// } + + if(srct.terType == ETerrainType::WATER && limitCoastSailing && hlpt.terType == ETerrainType::WATER && dir.x && dir.y) //diagonal move through water + { + int3 hlp1 = tile, + hlp2 = tile; + hlp1.x += dir.x; + hlp2.y += dir.y; + + if(map->getTile(hlp1).terType != ETerrainType::WATER || map->getTile(hlp2).terType != ETerrainType::WATER) + continue; + } + + if((indeterminate(onLand) || onLand == (hlpt.terType!=ETerrainType::WATER) ) + && hlpt.terType != ETerrainType::ROCK) + { + vec.push_back(hlp); + } + } +} + +int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, bool flying, int remainingMovePoints, bool checkLast) +{ + if(src == dest) //same tile + return 0; + + TerrainTile &s = map->getTile(src), + &d = map->getTile(dest); + + //get basic cost + int ret = h->getTileCost(d,s); + + if(d.blocked && flying) + { + bool freeFlying = h->getBonusesCount(Selector::typeSubtype(Bonus::FLYING_MOVEMENT, 1)) > 0; + + if(!freeFlying) + { + ret *= 1.4; //40% penalty for movement over blocked tile + } + } + else if (d.terType == ETerrainType::WATER) + { + if(h->boat && s.hasFavourableWinds() && d.hasFavourableWinds()) //Favourable Winds + ret *= 0.666; + else if (!h->boat && h->getBonusesCount(Selector::typeSubtype(Bonus::WATER_WALKING, 1)) > 0) + ret *= 1.4; //40% penalty for water walking + } + + if(src.x != dest.x && src.y != dest.y) //it's diagonal move + { + int old = ret; + ret *= 1.414213; + //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points + if(ret > remainingMovePoints && remainingMovePoints >= old) + { + return remainingMovePoints; + } + } + + + int left = remainingMovePoints-ret; + if(checkLast && left > 0 && remainingMovePoints-ret < 250) //it might be the last tile - if no further move possible we take all move points + { + std::vector vec; + vec.reserve(8); //optimization + getNeighbours(d, dest, vec, s.terType != ETerrainType::WATER, true); + for(auto & elem : vec) + { + int fcost = getMovementCost(h,dest, elem, flying, left, false); + if(fcost <= left) + { + return ret; + } + } + ret = remainingMovePoints; + } + return ret; +} + +void CGameState::apply(CPack *pack) +{ + ui16 typ = typeList.getTypeID(pack); + applierGs->apps[typ]->applyOnGS(this,pack); +} + +void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src, int movement) +{ + CPathfinder pathfinder(out, this, hero); + pathfinder.calculatePaths(src, movement); +} + +/** + * Tells if the tile is guarded by a monster as well as the position + * of the monster that will attack on it. + * + * @return int3(-1, -1, -1) if the tile is unguarded, or the position of + * the monster guarding the tile. + */ +std::vector CGameState::guardingCreatures (int3 pos) const +{ + std::vector guards; + const int3 originalPos = pos; + if (!map->isInTheMap(pos)) + return guards; + + const TerrainTile &posTile = map->getTile(pos); + if (posTile.visitable) + { + for (CGObjectInstance* obj : posTile.visitableObjects) + { + if(obj->blockVisit) + { + if (obj->ID == Obj::MONSTER) // Monster + guards.push_back(obj); + } + } + } + pos -= int3(1, 1, 0); // Start with top left. + for (int dx = 0; dx < 3; dx++) + { + for (int dy = 0; dy < 3; dy++) + { + if (map->isInTheMap(pos)) + { + const auto & tile = map->getTile(pos); + if (tile.visitable && (tile.isWater() == posTile.isWater())) + { + for (CGObjectInstance* obj : tile.visitableObjects) + { + if (obj->ID == Obj::MONSTER && map->checkForVisitableDir(pos, &map->getTile(originalPos), originalPos)) // Monster being able to attack investigated tile + { + guards.push_back(obj); + } + } + } + } + + pos.y++; + } + pos.y -= 3; + pos.x++; + } + return guards; + +} + +int3 CGameState::guardingCreaturePosition (int3 pos) const +{ + return gs->map->guardingCreaturePositions[pos.x][pos.y][pos.z]; +} + +bool CGameState::isVisible(int3 pos, PlayerColor player) +{ + if(player == PlayerColor::NEUTRAL) + return false; + return getPlayerTeam(player)->fogOfWarMap[pos.x][pos.y][pos.z]; +} + +bool CGameState::isVisible( const CGObjectInstance *obj, boost::optional player ) +{ + if(!player) + return true; + + if(*player == PlayerColor::NEUTRAL) //-> TODO ??? needed? + return false; + //object is visible when at least one blocked tile is visible + for(int fy=0; fy < obj->getHeight(); ++fy) + { + for(int fx=0; fx < obj->getWidth(); ++fx) + { + int3 pos = obj->pos + int3(-fx, -fy, 0); + + if ( map->isInTheMap(pos) && + obj->coveringAt(pos.x, pos.y) && + isVisible(pos, *player)) + return true; + } + } + return false; +} + +bool CGameState::checkForVisitableDir(const int3 & src, const int3 & dst) const +{ + const TerrainTile * pom = &map->getTile(dst); + return map->checkForVisitableDir(src, pom, dst); +} + +EVictoryLossCheckResult CGameState::checkForVictoryAndLoss(PlayerColor player) const +{ + const std::string & messageWonSelf = VLC->generaltexth->allTexts[659]; + const std::string & messageWonOther = VLC->generaltexth->allTexts[5]; + const std::string & messageLostSelf = VLC->generaltexth->allTexts[7]; + const std::string & messageLostOther = VLC->generaltexth->allTexts[8]; + + auto evaluateEvent = [=](const EventCondition & condition) + { + return this->checkForVictory(player, condition); + }; + + const PlayerState *p = CGameInfoCallback::getPlayer(player); + + //cheater or tester, but has entered the code... + if (p->enteredWinningCheatCode) + return EVictoryLossCheckResult::victory(messageWonSelf, messageWonOther); + + if (p->enteredLosingCheatCode) + return EVictoryLossCheckResult::defeat(messageLostSelf, messageLostOther); + + for (const TriggeredEvent & event : map->triggeredEvents) + { + if ((event.trigger.test(evaluateEvent))) + { + if (event.effect.type == EventEffect::VICTORY) + return EVictoryLossCheckResult::victory(event.onFulfill, event.effect.toOtherMessage); + + if (event.effect.type == EventEffect::DEFEAT) + return EVictoryLossCheckResult::defeat(event.onFulfill, event.effect.toOtherMessage); + } + } + + if (checkForStandardLoss(player)) + { + return EVictoryLossCheckResult::defeat(messageLostSelf, messageLostOther); + } + return EVictoryLossCheckResult(); +} + +bool CGameState::checkForVictory( PlayerColor player, const EventCondition & condition ) const +{ + const PlayerState *p = CGameInfoCallback::getPlayer(player); + switch (condition.condition) + { + case EventCondition::STANDARD_WIN: + { + return player == checkForStandardWin(); + } + case EventCondition::HAVE_ARTIFACT: //check if any hero has winning artifact + { + for(auto & elem : p->heroes) + if(elem->hasArt(condition.objectType)) + return true; + return false; + } + case EventCondition::HAVE_CREATURES: + { + //check if in players armies there is enough creatures + int total = 0; //creature counter + for(size_t i = 0; i < map->objects.size(); i++) + { + const CArmedInstance *ai = nullptr; + if(map->objects[i] + && map->objects[i]->tempOwner == player //object controlled by player + && (ai = dynamic_cast(map->objects[i].get()))) //contains army + { + for(auto & elem : ai->Slots()) //iterate through army + if(elem.second->type->idNumber == condition.objectType) //it's searched creature + total += elem.second->count; + } + } + return total >= condition.value; + } + case EventCondition::HAVE_RESOURCES: + { + return p->resources[condition.objectType] >= condition.value; + } + case EventCondition::HAVE_BUILDING: + { + if (condition.object) // specific town + { + const CGTownInstance *t = static_cast(condition.object); + return (t->tempOwner == player && t->hasBuilt(BuildingID(condition.objectType))); + } + else // any town + { + for (const CGTownInstance * t : p->towns) + { + if (t->hasBuilt(BuildingID(condition.objectType))) + return true; + } + return false; + } + } + case EventCondition::DESTROY: + { + if (condition.object) // mode A - destroy specific object of this type + { + if (auto hero = dynamic_cast(condition.object)) + return boost::range::find(gs->map->heroesOnMap, hero) == gs->map->heroesOnMap.end(); + else + return getObj(condition.object->id) == nullptr; + } + else + { + for(auto & elem : map->objects) // mode B - destroy all objects of this type + { + if(elem && elem->ID == condition.objectType) + return false; + } + return true; + } + } + case EventCondition::CONTROL: + { + // list of players that need to control object to fulfull condition + // NOTE: cgameinfocallback specified explicitly in order to get const version + auto & team = CGameInfoCallback::getPlayerTeam(player)->players; + + if (condition.object) // mode A - flag one specific object, like town + { + return team.count(condition.object->tempOwner) != 0; + } + else + { + for(auto & elem : map->objects) // mode B - flag all objects of this type + { + //check not flagged objs + if ( elem && elem->ID == condition.objectType && team.count(elem->tempOwner) == 0 ) + return false; + } + return true; + } + } + case EventCondition::TRANSPORT: + { + const CGTownInstance *t = static_cast(condition.object); + if((t->visitingHero && t->visitingHero->hasArt(condition.objectType)) + || (t->garrisonHero && t->garrisonHero->hasArt(condition.objectType))) + { + return true; + } + return false; + } + case EventCondition::DAYS_PASSED: + { + return gs->day > condition.value; + } + case EventCondition::IS_HUMAN: + { + return p->human ? condition.value == 1 : condition.value == 0; + } + case EventCondition::DAYS_WITHOUT_TOWN: + { + if (p->daysWithoutCastle) + return p->daysWithoutCastle.get() >= condition.value; + else + return false; + } + case EventCondition::CONST_VALUE: + { + return condition.value; // just convert to bool + } + } + assert(0); + return false; +} + +PlayerColor CGameState::checkForStandardWin() const +{ + //std victory condition is: + //all enemies lost + PlayerColor supposedWinner = PlayerColor::NEUTRAL; + TeamID winnerTeam = TeamID::NO_TEAM; + for(auto & elem : players) + { + if(elem.second.status == EPlayerStatus::INGAME && elem.first < PlayerColor::PLAYER_LIMIT) + { + if(supposedWinner == PlayerColor::NEUTRAL) + { + //first player remaining ingame - candidate for victory + supposedWinner = elem.second.color; + winnerTeam = elem.second.team; + } + else if(winnerTeam != elem.second.team) + { + //current candidate has enemy remaining in game -> no vicotry + return PlayerColor::NEUTRAL; + } + } + } + + return supposedWinner; +} + +bool CGameState::checkForStandardLoss( PlayerColor player ) const +{ + //std loss condition is: player lost all towns and heroes + const PlayerState &p = *CGameInfoCallback::getPlayer(player); + return !p.heroes.size() && !p.towns.size(); +} + +struct statsHLP +{ + typedef std::pair< PlayerColor, si64 > TStat; + //converts [] to vec[place] -> platers + static std::vector< std::vector< PlayerColor > > getRank( std::vector stats ) + { + std::sort(stats.begin(), stats.end(), statsHLP()); + + //put first element + std::vector< std::vector > ret; + std::vector tmp; + tmp.push_back( stats[0].first ); + ret.push_back( tmp ); + + //the rest of elements + for(int g=1; gpush_back( stats[g].first ); + } + else + { + //create next occupied rank + std::vector tmp; + tmp.push_back(stats[g].first); + ret.push_back(tmp); + } + } + + return ret; + } + + bool operator()(const TStat & a, const TStat & b) const + { + return a.second > b.second; + } + + static const CGHeroInstance * findBestHero(CGameState * gs, PlayerColor color) + { + std::vector > &h = gs->players[color].heroes; + if(!h.size()) + return nullptr; + //best hero will be that with highest exp + int best = 0; + for(int b=1; bexp > h[best]->exp) + { + best = b; + } + } + return h[best]; + } + + //calculates total number of artifacts that belong to given player + static int getNumberOfArts(const PlayerState * ps) + { + int ret = 0; + for(auto h : ps->heroes) + { + ret += h->artifactsInBackpack.size() + h->artifactsWorn.size(); + } + return ret; + } + + // get total strength of player army + static si64 getArmyStrength(const PlayerState * ps) + { + si64 str = 0; + + for(auto h : ps->heroes) + { + if(!h->inTownGarrison) //original h3 behavior + str += h->getArmyStrength(); + } + return str; + } +}; + +void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) +{ + auto playerInactive = [&](PlayerColor color) + { + return color == PlayerColor::NEUTRAL || players.at(color).status != EPlayerStatus::INGAME; + }; + +#define FILL_FIELD(FIELD, VAL_GETTER) \ + { \ + std::vector< std::pair< PlayerColor, si64 > > stats; \ + for(auto g = players.begin(); g != players.end(); ++g) \ + { \ + if(playerInactive(g->second.color)) \ + continue; \ + std::pair< PlayerColor, si64 > stat; \ + stat.first = g->second.color; \ + stat.second = VAL_GETTER; \ + stats.push_back(stat); \ + } \ + tgi.FIELD = statsHLP::getRank(stats); \ + } + + for(auto & elem : players) + { + if(!playerInactive(elem.second.color)) + tgi.playerColors.push_back(elem.second.color); + } + + if(level >= 1) //num of towns & num of heroes + { + //num of towns + FILL_FIELD(numOfTowns, g->second.towns.size()) + //num of heroes + FILL_FIELD(numOfHeroes, g->second.heroes.size()) + //best hero's portrait + for(auto g = players.cbegin(); g != players.cend(); ++g) + { + if(playerInactive(g->second.color)) + continue; + const CGHeroInstance * best = statsHLP::findBestHero(this, g->second.color); + InfoAboutHero iah; + iah.initFromHero(best, level >= 8); + iah.army.clear(); + tgi.colorToBestHero[g->second.color] = iah; + } + } + if(level >= 2) //gold + { + FILL_FIELD(gold, g->second.resources[Res::GOLD]) + } + if(level >= 2) //wood & ore + { + FILL_FIELD(woodOre, g->second.resources[Res::WOOD] + g->second.resources[Res::ORE]) + } + if(level >= 3) //mercury, sulfur, crystal, gems + { + FILL_FIELD(mercSulfCrystGems, g->second.resources[Res::MERCURY] + g->second.resources[Res::SULFUR] + g->second.resources[Res::CRYSTAL] + g->second.resources[Res::GEMS]) + } + if(level >= 4) //obelisks found + { + FILL_FIELD(obelisks, CGObelisk::visited[gs->getPlayerTeam(g->second.color)->id]) + } + if(level >= 5) //artifacts + { + FILL_FIELD(artifacts, statsHLP::getNumberOfArts(&g->second)) + } + if(level >= 6) //army strength + { + FILL_FIELD(army, statsHLP::getArmyStrength(&g->second)) + } + if(level >= 7) //income + { + //TODO:obtainPlayersStats - income + } + if(level >= 8) //best hero's stats + { + //already set in lvl 1 handling + } + if(level >= 9) //personality + { + for(auto g = players.cbegin(); g != players.cend(); ++g) + { + if(playerInactive(g->second.color)) //do nothing for neutral player + continue; + if(g->second.human) + { + tgi.personality[g->second.color] = EAiTactic::NONE; + } + else //AI + { + tgi.personality[g->second.color] = map->players[g->second.color.getNum()].aiTactic; + } + + } + } + if(level >= 10) //best creature + { + //best creatures belonging to player (highest AI value) + for(auto g = players.cbegin(); g != players.cend(); ++g) + { + if(playerInactive(g->second.color)) //do nothing for neutral player + continue; + int bestCre = -1; //best creature's ID + for(auto & elem : g->second.heroes) + { + for(auto it = elem->Slots().begin(); it != elem->Slots().end(); ++it) + { + int toCmp = it->second->type->idNumber; //ID of creature we should compare with the best one + if(bestCre == -1 || VLC->creh->creatures[bestCre]->AIValue < VLC->creh->creatures[toCmp]->AIValue) + { + bestCre = toCmp; + } + } + } + tgi.bestCreature[g->second.color] = bestCre; + } + } + +#undef FILL_FIELD +} + +std::map > CGameState::unusedHeroesFromPool() +{ + std::map > pool = hpool.heroesPool; + for ( auto i = players.cbegin() ; i != players.cend();i++) + for(auto j = i->second.availableHeroes.cbegin(); j != i->second.availableHeroes.cend(); j++) + if(*j) + pool.erase((**j).subID); + + return pool; +} + +void CGameState::buildBonusSystemTree() +{ + buildGlobalTeamPlayerTree(); + attachArmedObjects(); + + for(CGTownInstance *t : map->towns) + { + t->deserializationFix(); + } + // CStackInstance <-> CCreature, CStackInstance <-> CArmedInstance, CArtifactInstance <-> CArtifact + // are provided on initializing / deserializing +} + +void CGameState::deserializationFix() +{ + buildGlobalTeamPlayerTree(); + attachArmedObjects(); +} + +void CGameState::buildGlobalTeamPlayerTree() +{ + for(auto k=teams.begin(); k!=teams.end(); ++k) + { + TeamState *t = &k->second; + t->attachTo(&globalEffects); + + for(PlayerColor teamMember : k->second.players) + { + PlayerState *p = getPlayer(teamMember); + assert(p); + p->attachTo(t); + } + } +} + +void CGameState::attachArmedObjects() +{ + for(CGObjectInstance *obj : map->objects) + { + if(CArmedInstance *armed = dynamic_cast(obj)) + armed->whatShouldBeAttached()->attachTo(armed->whereShouldBeAttached(this)); + } +} + +void CGameState::giveHeroArtifact(CGHeroInstance *h, ArtifactID aid) +{ + CArtifact * const artifact = VLC->arth->artifacts[aid]; //pointer to constant object + CArtifactInstance *ai = CArtifactInstance::createNewArtifactInstance(artifact); + map->addNewArtifactInstance(ai); + ai->putAt(ArtifactLocation(h, ai->firstAvailableSlot(h))); +} + +std::set CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllowed /*= false*/) const +{ + std::set ret; + for(int i = 0; i < map->allowedHeroes.size(); i++) + if(map->allowedHeroes[i] || alsoIncludeNotAllowed) + ret.insert(HeroTypeID(i)); + + for(auto hero : map->heroesOnMap) //heroes instances initialization + { + if(hero->type) + ret -= hero->type->ID; + else + ret -= HeroTypeID(hero->subID); + } + + for(auto obj : map->objects) //prisons + if(obj && obj->ID == Obj::PRISON) + ret -= HeroTypeID(obj->subID); + + return ret; +} + +std::vector CGameState::generateCampaignHeroesToReplace(CrossoverHeroesList & crossoverHeroes) +{ + std::vector campaignHeroReplacements; + + //selecting heroes by type + for(auto obj : map->objects) + { + if(obj && obj->ID == Obj::HERO_PLACEHOLDER) + { + auto heroPlaceholder = dynamic_cast(obj.get()); + if(heroPlaceholder->subID != 0xFF) //select by type + { + auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [heroPlaceholder](CGHeroInstance * hero) + { + return hero->subID == heroPlaceholder->subID; + }); + + if(it != crossoverHeroes.heroesFromAnyPreviousScenarios.end()) + { + auto hero = *it; + crossoverHeroes.removeHeroFromBothLists(hero); + campaignHeroReplacements.push_back(CampaignHeroReplacement(CMemorySerializer::deepCopy(*hero).release(), heroPlaceholder->id)); + } + } + } + } + + //selecting heroes by power + range::sort(crossoverHeroes.heroesFromPreviousScenario, [](const CGHeroInstance * a, const CGHeroInstance * b) + { + return a->getHeroStrength() > b->getHeroStrength(); + }); //sort, descending strength + + // sort hero placeholders descending power + std::vector heroPlaceholders; + for(auto obj : map->objects) + { + if(obj && obj->ID == Obj::HERO_PLACEHOLDER) + { + auto heroPlaceholder = dynamic_cast(obj.get()); + if(heroPlaceholder->subID == 0xFF) //select by power + { + heroPlaceholders.push_back(heroPlaceholder); + } + } + } + range::sort(heroPlaceholders, [](const CGHeroPlaceholder * a, const CGHeroPlaceholder * b) + { + return a->power > b->power; + }); + + for(int i = 0; i < heroPlaceholders.size(); ++i) + { + auto heroPlaceholder = heroPlaceholders[i]; + if(crossoverHeroes.heroesFromPreviousScenario.size() > i) + { + auto hero = crossoverHeroes.heroesFromPreviousScenario[i]; + campaignHeroReplacements.push_back(CampaignHeroReplacement(CMemorySerializer::deepCopy(*hero).release(), heroPlaceholder->id)); + } + } + + return campaignHeroReplacements; +} + +void CGameState::replaceHeroesPlaceholders(const std::vector & campaignHeroReplacements) +{ + for(auto campaignHeroReplacement : campaignHeroReplacements) + { + auto heroPlaceholder = dynamic_cast(getObjInstance(campaignHeroReplacement.heroPlaceholderId)); + + CGHeroInstance *heroToPlace = campaignHeroReplacement.hero; + heroToPlace->id = campaignHeroReplacement.heroPlaceholderId; + heroToPlace->tempOwner = heroPlaceholder->tempOwner; + heroToPlace->pos = heroPlaceholder->pos; + heroToPlace->type = VLC->heroh->heroes[heroToPlace->subID]; + + for(auto &&i : heroToPlace->stacks) + i.second->type = VLC->creh->creatures[i.second->getCreatureID()]; + + auto fixArtifact = [&](CArtifactInstance * art) + { + art->artType = VLC->arth->artifacts[art->artType->id]; + gs->map->artInstances.push_back(art); + art->id = ArtifactInstanceID(gs->map->artInstances.size() - 1); + }; + + for(auto &&i : heroToPlace->artifactsWorn) + fixArtifact(i.second.artifact); + for(auto &&i : heroToPlace->artifactsInBackpack) + fixArtifact(i.artifact); + + map->heroesOnMap.push_back(heroToPlace); + map->objects[heroToPlace->id.getNum()] = heroToPlace; + map->addBlockVisTiles(heroToPlace); + + scenarioOps->campState->getCurrentScenario().placedCrossoverHeroes.push_back(heroToPlace); + } +} + +bool CGameState::isUsedHero(HeroTypeID hid) const +{ + return getUsedHero(hid); +} + +CGHeroInstance * CGameState::getUsedHero(HeroTypeID hid) const +{ + for(auto hero : map->heroesOnMap) //heroes instances initialization + { + if(hero->subID == hid.getNum()) + { + return hero; + } + } + + for(auto obj : map->objects) //prisons + { + if(obj && obj->ID == Obj::PRISON && obj->subID == hid.getNum()) + { + return dynamic_cast(obj.get()); + } + } + + return nullptr; +} + +CGPathNode::CGPathNode() +:coord(-1,-1,-1) +{ + accessible = NOT_SET; + land = 0; + moveRemains = 0; + turns = 255; + theNodeBefore = nullptr; +} + +bool CGPathNode::reachable() const +{ + return turns < 255; +} + +bool CPathsInfo::getPath( const int3 &dst, CGPath &out ) +{ + assert(isValid); + + out.nodes.clear(); + const CGPathNode *curnode = &nodes[dst.x][dst.y][dst.z]; + if(!curnode->theNodeBefore) + return false; + + + while(curnode) + { + CGPathNode cpn = *curnode; + curnode = curnode->theNodeBefore; + out.nodes.push_back(cpn); + } + return true; +} + +CPathsInfo::CPathsInfo( const int3 &Sizes ) +:sizes(Sizes) +{ + hero = nullptr; + nodes = new CGPathNode**[sizes.x]; + for(int i = 0; i < sizes.x; i++) + { + nodes[i] = new CGPathNode*[sizes.y]; + for (int j = 0; j < sizes.y; j++) + { + nodes[i][j] = new CGPathNode[sizes.z]; + } + } +} + +CPathsInfo::~CPathsInfo() +{ + for(int i = 0; i < sizes.x; i++) + { + for (int j = 0; j < sizes.y; j++) + { + delete [] nodes[i][j]; + } + delete [] nodes[i]; + } + delete [] nodes; +} + +int3 CGPath::startPos() const +{ + return nodes[nodes.size()-1].coord; +} + +int3 CGPath::endPos() const +{ + return nodes[0].coord; +} + +void CGPath::convert( ui8 mode ) +{ + if(mode==0) + { + for(auto & elem : nodes) + { + elem.coord = CGHeroInstance::convertPosition(elem.coord,true); + } + } +} + +PlayerState::PlayerState() + : color(-1), currentSelection(0xffffffff), enteredWinningCheatCode(0), + enteredLosingCheatCode(0), status(EPlayerStatus::INGAME) +{ + setNodeType(PLAYER); +} + +std::string PlayerState::nodeName() const +{ + return "Player " + (color.getNum() < VLC->generaltexth->capColors.size() ? VLC->generaltexth->capColors[color.getNum()] : boost::lexical_cast(color)); +} + +InfoAboutArmy::InfoAboutArmy(): + owner(PlayerColor::NEUTRAL) +{} + +InfoAboutArmy::InfoAboutArmy(const CArmedInstance *Army, bool detailed) +{ + initFromArmy(Army, detailed); +} + +void InfoAboutArmy::initFromArmy(const CArmedInstance *Army, bool detailed) +{ + army = ArmyDescriptor(Army, detailed); + owner = Army->tempOwner; + name = Army->getHoverText(); +} + +void InfoAboutHero::assign(const InfoAboutHero & iah) +{ + InfoAboutArmy::operator = (iah); + + details = (iah.details ? new Details(*iah.details) : nullptr); + hclass = iah.hclass; + portrait = iah.portrait; +} + +InfoAboutHero::InfoAboutHero(): + details(nullptr), + hclass(nullptr), + portrait(-1) +{} + +InfoAboutHero::InfoAboutHero(const InfoAboutHero & iah): + InfoAboutArmy() +{ + assign(iah); +} + +InfoAboutHero::InfoAboutHero(const CGHeroInstance *h, bool detailed) + : details(nullptr), + hclass(nullptr), + portrait(-1) +{ + initFromHero(h, detailed); +} + +InfoAboutHero::~InfoAboutHero() +{ + delete details; +} + +InfoAboutHero & InfoAboutHero::operator=(const InfoAboutHero & iah) +{ + assign(iah); + return *this; +} + +void InfoAboutHero::initFromHero(const CGHeroInstance *h, bool detailed) +{ + if(!h) + return; + + initFromArmy(h, detailed); + + hclass = h->type->heroClass; + name = h->name; + portrait = h->portrait; + + if(detailed) + { + //include details about hero + details = new Details; + details->luck = h->LuckVal(); + details->morale = h->MoraleVal(); + details->mana = h->mana; + details->primskills.resize(GameConstants::PRIMARY_SKILLS); + + for (int i = 0; i < GameConstants::PRIMARY_SKILLS ; i++) + { + details->primskills[i] = h->getPrimSkillLevel(static_cast(i)); + } + } +} + +InfoAboutTown::InfoAboutTown(): + details(nullptr), + tType(nullptr), + built(0), + fortLevel(0) +{ + +} + +InfoAboutTown::InfoAboutTown(const CGTownInstance *t, bool detailed) +{ + initFromTown(t, detailed); +} + +InfoAboutTown::~InfoAboutTown() +{ + delete details; +} + +void InfoAboutTown::initFromTown(const CGTownInstance *t, bool detailed) +{ + initFromArmy(t, detailed); + army = ArmyDescriptor(t->getUpperArmy(), detailed); + built = t->builded; + fortLevel = t->fortLevel(); + name = t->name; + tType = t->town; + + if(detailed) + { + //include details about hero + details = new Details; + TResources income = t->dailyIncome(); + details->goldIncome = income[Res::GOLD]; + details->customRes = t->hasBuilt(BuildingID::RESOURCE_SILO); + details->hallLevel = t->hallLevel(); + details->garrisonedHero = t->garrisonHero; + } +} + +ArmyDescriptor::ArmyDescriptor(const CArmedInstance *army, bool detailed) + : isDetailed(detailed) +{ + for(auto & elem : army->Slots()) + { + if(detailed) + (*this)[elem.first] = *elem.second; + else + (*this)[elem.first] = CStackBasicDescriptor(elem.second->type, elem.second->getQuantityID()); + } +} + +ArmyDescriptor::ArmyDescriptor() + : isDetailed(false) +{ + +} + +int ArmyDescriptor::getStrength() const +{ + ui64 ret = 0; + if(isDetailed) + { + for(auto & elem : *this) + ret += elem.second.type->AIValue * elem.second.count; + } + else + { + for(auto & elem : *this) + ret += elem.second.type->AIValue * CCreature::estimateCreatureCount(elem.second.count); + } + return ret; +} + +DuelParameters::SideSettings::StackSettings::StackSettings() + : type(CreatureID::NONE), count(0) +{ +} + +DuelParameters::SideSettings::StackSettings::StackSettings(CreatureID Type, si32 Count) + : type(Type), count(Count) +{ +} + +DuelParameters::SideSettings::SideSettings() +{ + heroId = -1; +} + +DuelParameters::DuelParameters(): + terType(ETerrainType::DIRT), + bfieldType(BFieldType::ROCKLANDS) +{ +} + +DuelParameters DuelParameters::fromJSON(const std::string &fname) +{ + DuelParameters ret; + + const JsonNode duelData(ResourceID("DATA/" + fname, EResType::TEXT)); + ret.terType = ETerrainType((int)duelData["terType"].Float()); + ret.bfieldType = BFieldType((int)duelData["bfieldType"].Float()); + for(const JsonNode &n : duelData["sides"].Vector()) + { + SideSettings &ss = ret.sides[(int)n["side"].Float()]; + int i = 0; + for(const JsonNode &stackNode : n["army"].Vector()) + { + ss.stacks[i].type = CreatureID((si32)stackNode.Vector()[0].Float()); + ss.stacks[i].count = stackNode.Vector()[1].Float(); + i++; + } + + if(n["heroid"].getType() == JsonNode::DATA_FLOAT) + ss.heroId = n["heroid"].Float(); + else + ss.heroId = -1; + + for(const JsonNode &entry : n["heroPrimSkills"].Vector()) + ss.heroPrimSkills.push_back(entry.Float()); + + for(const JsonNode &skillNode : n["heroSecSkills"].Vector()) + { + std::pair secSkill; + secSkill.first = skillNode.Vector()[0].Float(); + secSkill.second = skillNode.Vector()[1].Float(); + ss.heroSecSkills.push_back(secSkill); + } + + assert(ss.heroPrimSkills.empty() || ss.heroPrimSkills.size() == GameConstants::PRIMARY_SKILLS); + + if(ss.heroId != -1) + { + const JsonNode & spells = n["spells"]; + if(spells.getType() == JsonNode::DATA_STRING && spells.String() == "all") + { + for(auto spell : VLC->spellh->objects) + if(spell->id <= SpellID::SUMMON_AIR_ELEMENTAL) + ss.spells.insert(spell->id); + } + else + for(const JsonNode &spell : n["spells"].Vector()) + ss.spells.insert(SpellID(spell.Float())); + } + } + + for(const JsonNode &n : duelData["obstacles"].Vector()) + { + auto oi = make_shared(); + if(n.getType() == JsonNode::DATA_VECTOR) + { + oi->ID = n.Vector()[0].Float(); + oi->pos = n.Vector()[1].Float(); + } + else + { + assert(n.getType() == JsonNode::DATA_FLOAT); + oi->ID = 21; + oi->pos = n.Float(); + } + oi->uniqueID = ret.obstacles.size(); + ret.obstacles.push_back(oi); + } + + for(const JsonNode &n : duelData["creatures"].Vector()) + { + CusomCreature cc; + cc.id = n["id"].Float(); + +#define retrieve(name) \ + if(n[ #name ].getType() == JsonNode::DATA_FLOAT)\ + cc.name = n[ #name ].Float(); \ + else \ + cc.name = -1; + + retrieve(attack); + retrieve(defense); + retrieve(HP); + retrieve(dmg); + retrieve(shoots); + retrieve(speed); + ret.creatures.push_back(cc); + } + + return ret; +} + +TeamState::TeamState() +{ + setNodeType(TEAM); +} + +void CPathfinder::initializeGraph() +{ + CGPathNode ***graph = out.nodes; + for(size_t i=0; i < out.sizes.x; ++i) + { + for(size_t j=0; j < out.sizes.y; ++j) + { + for(size_t k=0; k < out.sizes.z; ++k) + { + curPos = int3(i,j,k); + const TerrainTile *tinfo = &gs->map->getTile(int3(i, j, k)); + CGPathNode &node = graph[i][j][k]; + + node.accessible = evaluateAccessibility(tinfo); + node.turns = 0xff; + node.moveRemains = 0; + node.coord.x = i; + node.coord.y = j; + node.coord.z = k; + node.land = tinfo->terType != ETerrainType::WATER; + node.theNodeBefore = nullptr; + } + } + } +} + +void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*= -1*/) +{ + assert(hero); + assert(hero == getHero(hero->id)); + if(src.x < 0) + src = hero->getPosition(false); + if(movement < 0) + movement = hero->movement; + bool flying = hero->hasBonusOfType(Bonus::FLYING_MOVEMENT); + int maxMovePointsLand = hero->maxMovePoints(true); + int maxMovePointsWater = hero->maxMovePoints(false); + + auto maxMovePoints = [&](CGPathNode *cp) -> int + { + return cp->land ? maxMovePointsLand : maxMovePointsWater; + }; + + out.hero = hero; + out.hpos = src; + + if(!gs->map->isInTheMap(src)/* || !gs->map->isInTheMap(dest)*/) //check input + { + logGlobal->errorStream() << "CGameState::calculatePaths: Hero outside the gs->map? How dare you..."; + return; + } + + initializeGraph(); + + + //initial tile - set cost on 0 and add to the queue + CGPathNode &initialNode = *getNode(src); + initialNode.turns = 0; + initialNode.moveRemains = movement; + mq.push_back(&initialNode); + + std::vector neighbours; + neighbours.reserve(16); + while(!mq.empty()) + { + cp = mq.front(); + mq.pop_front(); + + const int3 sourceGuardPosition = gs->map->guardingCreaturePositions[cp->coord.x][cp->coord.y][cp->coord.z]; + bool guardedSource = (sourceGuardPosition != int3(-1, -1, -1) && cp->coord != src); + ct = &gs->map->getTile(cp->coord); + + int movement = cp->moveRemains, turn = cp->turns; + if(!movement) + { + movement = maxMovePoints(cp); + turn++; + } + + + //add accessible neighbouring nodes to the queue + neighbours.clear(); + + //handling subterranean gate => it's exit is the only neighbour + bool subterraneanEntry = (ct->topVisitableId() == Obj::SUBTERRANEAN_GATE && useSubterraneanGates); + if(subterraneanEntry) + { + //try finding the exit gate + if(const CGObjectInstance *outGate = getObj(CGTeleport::getMatchingGate(ct->visitableObjects.back()->id), false)) + { + const int3 outPos = outGate->visitablePos(); + //gs->getNeighbours(*getTile(outPos), outPos, neighbours, boost::logic::indeterminate, !cp->land); + neighbours.push_back(outPos); + } + else + { + //gate with no exit (blocked) -> do nothing with this node + continue; + } + } + + gs->getNeighbours(*ct, cp->coord, neighbours, boost::logic::indeterminate, !cp->land); + + for(auto & neighbour : neighbours) + { + const int3 &n = neighbour; //current neighbor + dp = getNode(n); + dt = &gs->map->getTile(n); + destTopVisObjID = dt->topVisitableId(); + + useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark + + int turnAtNextTile = turn; + + + const bool destIsGuardian = sourceGuardPosition == n; + + if(!goodForLandSeaTransition()) + continue; + + if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED ) + continue; + + //special case -> hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile + if(cp->accessible == CGPathNode::VISITABLE && guardedSource && cp->theNodeBefore->land && ct->topVisitableId() == Obj::BOAT) + guardedSource = false; + + int cost = gs->getMovementCost(hero, cp->coord, dp->coord, flying, movement); + + //special case -> moving from src Subterranean gate to dest gate -> it's free + if(subterraneanEntry && destTopVisObjID == Obj::SUBTERRANEAN_GATE && cp->coord.z != dp->coord.z) + cost = 0; + + int remains = movement - cost; + + if(useEmbarkCost) + { + remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1); + cost = movement - remains; + } + + if(remains < 0) + { + //occurs rarely, when hero with low movepoints tries to leave the road + turnAtNextTile++; + int moveAtNextTile = maxMovePoints(cp); + cost = gs->getMovementCost(hero, cp->coord, dp->coord, flying, moveAtNextTile); //cost must be updated, movement points changed :( + remains = moveAtNextTile - cost; + } + + if((dp->turns==0xff //we haven't been here before + || dp->turns > turnAtNextTile + || (dp->turns >= turnAtNextTile && dp->moveRemains < remains)) //this route is faster + && (!guardedSource || destIsGuardian)) // Can step into tile of guard + { + + assert(dp != cp->theNodeBefore); //two tiles can't point to each other + dp->moveRemains = remains; + dp->turns = turnAtNextTile; + dp->theNodeBefore = cp; + + const bool guardedDst = gs->map->guardingCreaturePositions[dp->coord.x][dp->coord.y][dp->coord.z].valid() + && dp->accessible == CGPathNode::BLOCKVIS; + + if (dp->accessible == CGPathNode::ACCESSIBLE + || (useEmbarkCost && allowEmbarkAndDisembark) + || destTopVisObjID == Obj::SUBTERRANEAN_GATE + || (guardedDst && !guardedSource)) // Can step into a hostile tile once. + { + mq.push_back(dp); + } + } + } //neighbours loop + } //queue loop + + out.isValid = true; +} + +CGPathNode *CPathfinder::getNode(const int3 &coord) +{ + return &out.nodes[coord.x][coord.y][coord.z]; +} + +bool CPathfinder::canMoveBetween(const int3 &a, const int3 &b) const +{ + return gs->checkForVisitableDir(a, b) && gs->checkForVisitableDir(b, a); +} + +CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const TerrainTile *tinfo) const +{ + CGPathNode::EAccessibility ret = (tinfo->blocked ? CGPathNode::BLOCKED : CGPathNode::ACCESSIBLE); + + + if(tinfo->terType == ETerrainType::ROCK || !FoW[curPos.x][curPos.y][curPos.z]) + return CGPathNode::BLOCKED; + + if(tinfo->visitable) + { + if(tinfo->visitableObjects.front()->ID == Obj::SANCTUARY && tinfo->visitableObjects.back()->ID == Obj::HERO && tinfo->visitableObjects.back()->tempOwner != hero->tempOwner) //non-owned hero stands on Sanctuary + { + return CGPathNode::BLOCKED; + } + else + { + for(const CGObjectInstance *obj : tinfo->visitableObjects) + { + if(obj->passableFor(hero->tempOwner)) //special object instance specific passableness flag - overwrites other accessibility flags + { + ret = CGPathNode::ACCESSIBLE; + } + else if(obj->blockVisit) + { + return CGPathNode::BLOCKVIS; + } + else if(obj->ID != Obj::EVENT) //pathfinder should ignore placed events + { + ret = CGPathNode::VISITABLE; + } + } + } + } + else if (gs->map->guardingCreaturePositions[curPos.x][curPos.y][curPos.z].valid() + && !tinfo->blocked) + { + // Monster close by; blocked visit for battle. + return CGPathNode::BLOCKVIS; + } + + return ret; +} + +bool CPathfinder::goodForLandSeaTransition() +{ + if(cp->land != dp->land) //hero can traverse land<->sea only in special circumstances + { + if(cp->land) //from land to sea -> embark or assault hero on boat + { + if(dp->accessible == CGPathNode::ACCESSIBLE || destTopVisObjID < 0) //cannot enter empty water tile from land -> it has to be visitable + return false; + if(destTopVisObjID != Obj::HERO && destTopVisObjID != Obj::BOAT) //only boat or hero can be accessed from land + return false; + if(destTopVisObjID == Obj::BOAT) + useEmbarkCost = 1; + } + else //disembark + { + //can disembark only on coastal tiles + if(!dt->isCoastal()) + return false; + + //tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast + if( (dp->accessible != CGPathNode::ACCESSIBLE && (dp->accessible != CGPathNode::BLOCKVIS || dt->blocked)) + || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate + return false;; + + useEmbarkCost = 2; + } + } + + return true; +} + +CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) : CGameInfoCallback(_gs, boost::optional()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap) +{ + useSubterraneanGates = true; + allowEmbarkAndDisembark = true; +} + +EVictoryLossCheckResult::EVictoryLossCheckResult() : + intValue(0) +{ +} + +EVictoryLossCheckResult::EVictoryLossCheckResult(si32 intValue, std::string toSelf, std::string toOthers) : + messageToSelf(toSelf), + messageToOthers(toOthers), + intValue(intValue) +{ +} + +bool EVictoryLossCheckResult::operator==(EVictoryLossCheckResult const & other) const +{ + return intValue == other.intValue; +} + +bool EVictoryLossCheckResult::operator!=(EVictoryLossCheckResult const & other) const +{ + return intValue != other.intValue; +} + +bool EVictoryLossCheckResult::victory() const +{ + return intValue == VICTORY; +} + +bool EVictoryLossCheckResult::loss() const +{ + return intValue == DEFEAT; +} + +EVictoryLossCheckResult EVictoryLossCheckResult::invert() +{ + return EVictoryLossCheckResult(-intValue, messageToOthers, messageToSelf); +} + +EVictoryLossCheckResult EVictoryLossCheckResult::victory(std::string toSelf, std::string toOthers) +{ + return EVictoryLossCheckResult(VICTORY, toSelf, toOthers); +} + +EVictoryLossCheckResult EVictoryLossCheckResult::defeat(std::string toSelf, std::string toOthers) +{ + return EVictoryLossCheckResult(DEFEAT, toSelf, toOthers); +} + +std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult) +{ + os << victoryLossCheckResult.messageToSelf; + return os; +} + +CRandomGenerator & CGameState::getRandomGenerator() +{ + return rand; +} diff --git a/lib/CGameState.h b/lib/CGameState.h index ac56c2455..0d8a8c8f2 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -14,7 +14,6 @@ #include "IGameCallback.h" #include "ResourceSet.h" #include "int3.h" -#include "CObjectHandler.h" #include "CRandomGenerator.h" /* @@ -167,6 +166,7 @@ public: ObjectInstanceID currentSelection; //id of hero/town, 0xffffffff if none TeamID team; TResources resources; + std::set visitedObjects; // as a std::set, since most accesses here will be from visited status checks std::vector > heroes; std::vector > towns; std::vector > availableHeroes; //heroes available in taverns @@ -183,7 +183,7 @@ public: template void serialize(Handler &h, const int version) { h & color & human & currentSelection & team & resources & status; - h & heroes & towns & availableHeroes & dwellings; + h & heroes & towns & availableHeroes & dwellings & visitedObjects; h & getBonusList(); //FIXME FIXME FIXME h & status & daysWithoutCastle; h & enteredLosingCheatCode & enteredWinningCheatCode; diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 25baa2b1c..b6d2f9885 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -290,7 +290,6 @@ CGeneralTextHandler::CGeneralTextHandler() readToVector("DATA/HEROSCRN.TXT", heroscrn); readToVector("DATA/TENTCOLR.TXT", tentColors); readToVector("DATA/SKILLLEV.TXT", levels); - readToVector("DATA/OBJNAMES.TXT", names); localizedTexts = JsonNode(ResourceID("config/translate.json", EResType::TEXT)); diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index 636d26aae..c4ae01596 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -112,7 +112,6 @@ public: std::vector victoryConditions; //objects - std::vector names; //vector of objects; i-th object in vector has subnumber i std::vector creGens; //names of creatures' generators std::vector creGens4; //names of multiple creatures' generators std::vector advobtxt; diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 361398373..1a8414dbd 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -10,9 +10,11 @@ #include "CCreatureHandler.h" #include "CModHandler.h" #include "CTownHandler.h" -#include "CObjectHandler.h" //for hero specialty +#include "mapObjects/CObjectHandler.h" //for hero specialty #include +#include "mapObjects/CObjectClassesHandler.h" + /* * CHeroHandler.cpp, part of VCMI engine * @@ -102,13 +104,12 @@ CHeroClass *CHeroClassHandler::loadFromJson(const JsonNode & node) heroClass->imageBattleFemale = node["animation"]["battle"]["female"].String(); heroClass->imageBattleMale = node["animation"]["battle"]["male"].String(); + //MODS COMPATIBILITY FOR 0.96 heroClass->imageMapFemale = node["animation"]["map"]["female"].String(); heroClass->imageMapMale = node["animation"]["map"]["male"].String(); heroClass->name = node["name"].String(); heroClass->affinity = vstd::find_pos(affinityStr, node["affinity"].String()); - if (heroClass->affinity >= 2) //FIXME: MODS COMPATIBILITY - heroClass->affinity = 0; for(const std::string & pSkill : PrimarySkill::names) { @@ -122,15 +123,11 @@ CHeroClass *CHeroClassHandler::loadFromJson(const JsonNode & node) heroClass->secSkillProbability.push_back(node["secondarySkills"][secSkill].Float()); } - //FIXME: MODS COMPATIBILITY - if (!node["commander"].isNull()) + VLC->modh->identifiers.requestIdentifier ("creature", node["commander"], + [=](si32 commanderID) { - VLC->modh->identifiers.requestIdentifier ("creature", node["commander"], - [=](si32 commanderID) - { - heroClass->commander = VLC->creh->creatures[commanderID]; - }); - } + heroClass->commander = VLC->creh->creatures[commanderID]; + }); heroClass->defaultTavernChance = node["defaultTavern"].Float(); for(auto & tavern : node["tavern"].Struct()) @@ -200,6 +197,14 @@ void CHeroClassHandler::loadObject(std::string scope, std::string name, const Js heroClasses.push_back(object); + VLC->modh->identifiers.requestIdentifier(scope, "object", "hero", [=](si32 index) + { + JsonNode classConf; + classConf["heroClass"].String() = name; + classConf.setMeta(scope); + VLC->objtypeh->loadSubObject(name, classConf, index, object->id); + }); + VLC->modh->identifiers.registerObject(scope, "heroClass", name, object->id); } @@ -211,6 +216,14 @@ void CHeroClassHandler::loadObject(std::string scope, std::string name, const Js assert(heroClasses[index] == nullptr); // ensure that this id was not loaded before heroClasses[index] = object; + VLC->modh->identifiers.requestIdentifier(scope, "object", "hero", [=](si32 index) + { + JsonNode classConf = data["mapObject"]; + classConf["heroClass"].String() = name; + classConf.setMeta(scope); + VLC->objtypeh->loadSubObject(name, classConf, index, object->id); + }); + VLC->modh->identifiers.registerObject(scope, "heroClass", name, object->id); } @@ -231,16 +244,14 @@ void CHeroClassHandler::afterLoadFinalization() } } - ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::HERO, 0).front(); for (CHeroClass * hc : heroClasses) { - base.animationFile = hc->imageMapMale; - base.subid = hc->id; - - // replace existing (if any) and add new template. - // Necessary for objects added via mods that don't have any templates in H3 - VLC->dobjinfo->eraseAll(Obj::HERO, hc->id); - VLC->dobjinfo->registerTemplate(base); + if (!hc->imageMapMale.empty()) + { + JsonNode templ; + templ["animation"].String() = hc->imageMapMale; + VLC->objtypeh->getHandlerFor(Obj::HERO, hc->id)->addTemplate(templ); + } } } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 66abd0eb3..f426a13a6 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -6,20 +6,6 @@ include_directories(${Boost_INCLUDE_DIRS} ${SDL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} set(lib_SRCS StdInc.cpp - IGameCallback.cpp - CGameInfoCallback.cpp - CGameState.cpp - CObjectHandler.cpp - Connection.cpp - NetPacksLib.cpp - - registerTypes/RegisterTypes.cpp - registerTypes/TypesClientPacks1.cpp - registerTypes/TypesClientPacks2.cpp - registerTypes/TypesMapObjects1.cpp - registerTypes/TypesMapObjects2.cpp - registerTypes/TypesPregamePacks.cpp - registerTypes/TypesServerPacks.cpp filesystem/AdapterLoaders.cpp filesystem/CCompressedStream.cpp @@ -33,6 +19,22 @@ set(lib_SRCS filesystem/Filesystem.cpp filesystem/ResourceID.cpp + mapObjects/CArmedInstance.cpp + mapObjects/CBank.cpp + mapObjects/CGHeroInstance.cpp + mapObjects/CGMarket.cpp + mapObjects/CGPandoraBox.cpp + mapObjects/CGTownInstance.cpp + mapObjects/CObjectClassesHandler.cpp + mapObjects/CObjectHandler.cpp + mapObjects/CommonConstructors.cpp + mapObjects/CQuest.cpp + mapObjects/CRewardableConstructor.cpp + mapObjects/CRewardableObject.cpp + mapObjects/JsonRandom.cpp + mapObjects/MiscObjects.cpp + mapObjects/ObjectTemplate.cpp + logging/CBasicLogConfigurator.cpp logging/CLogger.cpp @@ -63,7 +65,6 @@ set(lib_SRCS CConsoleHandler.cpp CCreatureHandler.cpp CCreatureSet.cpp - CDefObjInfoHandler.cpp CGameInterface.cpp CGeneralTextHandler.cpp CHeroHandler.cpp @@ -81,6 +82,21 @@ set(lib_SRCS ResourceSet.cpp VCMI_Lib.cpp VCMIDirs.cpp + + IGameCallback.cpp + CGameInfoCallback.cpp + CGameState.cpp + Connection.cpp + NetPacksLib.cpp + + registerTypes/RegisterTypes.cpp + registerTypes/TypesClientPacks1.cpp + registerTypes/TypesClientPacks2.cpp + registerTypes/TypesMapObjects1.cpp + registerTypes/TypesMapObjects2.cpp + registerTypes/TypesMapObjects3.cpp + registerTypes/TypesPregamePacks.cpp + registerTypes/TypesServerPacks.cpp ) set(lib_HEADERS @@ -89,6 +105,9 @@ set(lib_HEADERS filesystem/CInputStream.h filesystem/ISimpleResourceLoader.h + mapObjects/MapObjects.h + + CSoundBase.h AI_Base.h CondSh.h ConstTransitivePtr.h @@ -118,5 +137,5 @@ set_target_properties(vcmi PROPERTIES ${PCH_PROPERTIES}) cotire(vcmi) if (NOT APPLE) # Already inside vcmiclient bundle - install(TARGETS vcmi DESTINATION ${LIB_DIR}) + install(TARGETS vcmi DESTINATION ${LIB_DIR}) endif() diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 9b7701dee..9cd4b8072 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -1,6 +1,6 @@ #include "StdInc.h" #include "CModHandler.h" -#include "CDefObjInfoHandler.h" +#include "mapObjects/CObjectClassesHandler.h" #include "JsonNode.h" #include "filesystem/Filesystem.h" #include "filesystem/AdapterLoaders.h" @@ -10,7 +10,7 @@ #include "CArtHandler.h" #include "CTownHandler.h" #include "CHeroHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "StringConstants.h" #include "CStopWatch.h" #include "IHandlerBase.h" @@ -26,6 +26,11 @@ * */ +CIdentifierStorage::CIdentifierStorage(): + state(LOADING) +{ +} + void CIdentifierStorage::checkIdentifier(std::string & ID) { if (boost::algorithm::ends_with(ID, ".")) @@ -81,7 +86,10 @@ void CIdentifierStorage::requestIdentifier(ObjectCallback callback) assert(!callback.localScope.empty()); - scheduledRequests.push_back(callback); + if (state != FINISHED) // enqueue request if loading is still in progress + scheduledRequests.push_back(callback); + else // execute immediately for "late" requests + resolveIdentifier(callback); } void CIdentifierStorage::requestIdentifier(std::string scope, std::string type, std::string name, const std::function & callback) @@ -120,6 +128,19 @@ void CIdentifierStorage::tryRequestIdentifier(std::string type, const JsonNode & requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback, true)); } +boost::optional CIdentifierStorage::getIdentifier(std::string scope, std::string type, std::string name, bool silent) +{ + auto pair = splitString(name, ':'); // remoteScope:name + auto idList = getPossibleIdentifiers(ObjectCallback(scope, pair.first, type, pair.second, std::function(), silent)); + + if (idList.size() == 1) + return idList.front().id; + if (!silent) + logGlobal->errorStream() << "Failed to resolve identifier " << name << " of type " << type << " from mod " << scope; + + return boost::optional(); +} + boost::optional CIdentifierStorage::getIdentifier(std::string type, const JsonNode & name, bool silent) { auto pair = splitString(name.String(), ':'); // remoteScope:name @@ -128,7 +149,7 @@ boost::optional CIdentifierStorage::getIdentifier(std::string type, const if (idList.size() == 1) return idList.front().id; if (!silent) - logGlobal->errorStream() << "Failed to resolve identifier " << name.String() << " from mod " << type; + logGlobal->errorStream() << "Failed to resolve identifier " << name.String() << " of type " << type << " from mod " << name.meta; return boost::optional(); } @@ -142,7 +163,7 @@ boost::optional CIdentifierStorage::getIdentifier(const JsonNode & name, b if (idList.size() == 1) return idList.front().id; if (!silent) - logGlobal->errorStream() << "Failed to resolve identifier " << name.String() << " from mod " << name.meta; + logGlobal->errorStream() << "Failed to resolve identifier " << name.String() << " of type " << pair2.first << " from mod " << name.meta; return boost::optional(); } @@ -210,7 +231,9 @@ bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request) } if (request.optional && identifiers.empty()) // failed to resolve optinal ID + { return true; + } // error found. Try to generate some debug info if (identifiers.size() == 0) @@ -229,22 +252,25 @@ bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request) void CIdentifierStorage::finalize() { + state = FINALIZING; bool errorsFound = false; - for(const ObjectCallback & request : scheduledRequests) + //Note: we may receive new requests during resolution phase -> end may change -> range for can't be used + for(auto it = scheduledRequests.begin(); it != scheduledRequests.end(); it++) { - errorsFound |= !resolveIdentifier(request); + errorsFound |= !resolveIdentifier(*it); } if (errorsFound) { for(auto object : registeredObjects) { - logGlobal->traceStream() << object.first << " -> " << object.second.id; + logGlobal->traceStream() << object.second.scope << " : " << object.first << " -> " << object.second.id; } logGlobal->errorStream() << "All known identifiers were dumped into log file"; } assert(errorsFound == false); + state = FINISHED; } CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objectName): @@ -347,10 +373,11 @@ CContentHandler::CContentHandler() handlers.insert(std::make_pair("artifacts", ContentTypeHandler(VLC->arth, "artifact"))); handlers.insert(std::make_pair("creatures", ContentTypeHandler(VLC->creh, "creature"))); handlers.insert(std::make_pair("factions", ContentTypeHandler(VLC->townh, "faction"))); + handlers.insert(std::make_pair("objects", ContentTypeHandler(VLC->objtypeh, "object"))); handlers.insert(std::make_pair("heroes", ContentTypeHandler(VLC->heroh, "hero"))); - handlers.insert(std::make_pair("spells", ContentTypeHandler(VLC->spellh, "spell"))); + handlers.insert(std::make_pair("spells", ContentTypeHandler(VLC->spellh, "spell"))); - //TODO: bonuses, something else? + //TODO: any other types of moddables? } bool CContentHandler::preloadModData(std::string modName, JsonNode modConfig, bool validate) diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 080acc2e1..279e634e7 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -25,6 +25,13 @@ class IHandlerBase; /// if possible, objects ID's should be in format ., camelCase e.g. "creature.grandElf" class CIdentifierStorage { + enum ELoadingState + { + LOADING, + FINALIZING, + FINISHED + }; + struct ObjectCallback // entry created on ID request { std::string localScope; /// scope from which this ID was requested @@ -52,6 +59,8 @@ class CIdentifierStorage std::multimap registeredObjects; std::vector scheduledRequests; + ELoadingState state; + /// Check if identifier can be valid (camelCase, point as separator) void checkIdentifier(std::string & ID); @@ -59,6 +68,7 @@ class CIdentifierStorage bool resolveIdentifier(const ObjectCallback & callback); std::vector getPossibleIdentifiers(const ObjectCallback & callback); public: + CIdentifierStorage(); /// request identifier for specific object name. /// Function callback will be called during ID resolution phase of loading void requestIdentifier(std::string scope, std::string type, std::string name, const std::function & callback); @@ -70,6 +80,7 @@ public: void tryRequestIdentifier(std::string type, const JsonNode & name, const std::function & callback); /// get identifier immediately. If identifier is not know and not silent call will result in error message + boost::optional getIdentifier(std::string scope, std::string type, std::string name, bool silent = false); boost::optional getIdentifier(std::string type, const JsonNode & name, bool silent = false); boost::optional getIdentifier(const JsonNode & name, bool silent = false); @@ -81,7 +92,7 @@ public: template void serialize(Handler &h, const int version) { - h & registeredObjects; + h & registeredObjects & state; } }; diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp deleted file mode 100644 index e594c33a2..000000000 --- a/lib/CObjectHandler.cpp +++ /dev/null @@ -1,7777 +0,0 @@ -/* - * CObjectHandler.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#include "StdInc.h" -#include "CObjectHandler.h" - -#include "CDefObjInfoHandler.h" -#include "CGeneralTextHandler.h" -#include "CDefObjInfoHandler.h" -#include "CHeroHandler.h" -#include "CSpellHandler.h" -#include "CModHandler.h" -#include "../client/CSoundBase.h" -#include "CTownHandler.h" -#include "CCreatureHandler.h" -#include "VCMI_Lib.h" -#include "IGameCallback.h" -#include "CGameState.h" -#include "NetPacks.h" -#include "StartInfo.h" -#include "mapping/CMap.h" -#include -#include "CBuildingHandler.h" -#include "JsonNode.h" -#include "filesystem/Filesystem.h" - -using namespace boost::assign; - -std::map > > CGTeleport::objs; -std::vector > CGTeleport::gates; -IGameCallback * IObjectInterface::cb = nullptr; -std::map > CGKeys::playerKeyMap; -std::map > CGMagi::eyelist; -ui8 CGObelisk::obeliskCount; //how many obelisks are on map -std::map CGObelisk::visited; //map: team_id => how many obelisks has been visited - -std::vector CGTownInstance::merchantArtifacts; -std::vector CGTownInstance::universitySkills; - -///helpers -static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1) -{ - OpenWindow ow; - ow.window = type; - ow.id1 = id1; - ow.id2 = id2; - IObjectInterface::cb->sendAndApply(&ow); -} - -static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) -{ - InfoWindow iw; - iw.soundID = soundID; - iw.player = playerID; - iw.text.addTxt(MetaString::ADVOB_TXT,txtID); - IObjectInterface::cb->sendAndApply(&iw); -} - -/*static void showInfoDialog(const ObjectInstanceID heroID, const ui32 txtID, const ui16 soundID) -{ - const PlayerColor playerID = IObjectInterface::cb->getOwner(heroID); - showInfoDialog(playerID,txtID,soundID); -}*/ - -static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID) -{ - const PlayerColor playerID = h->getOwner(); - showInfoDialog(playerID,txtID,soundID); -} - -static std::string & visitedTxt(const bool visited) -{ - int id = visited ? 352 : 353; - return VLC->generaltexth->allTexts[id]; -} - -///IObjectInterface -void IObjectInterface::onHeroVisit(const CGHeroInstance * h) const -{} - -void IObjectInterface::onHeroLeave(const CGHeroInstance * h) const -{} - -void IObjectInterface::newTurn () const -{} - -IObjectInterface::~IObjectInterface() -{} - -IObjectInterface::IObjectInterface() -{} - -void IObjectInterface::initObj() -{} - -void IObjectInterface::setProperty( ui8 what, ui32 val ) -{} - -bool IObjectInterface::wasVisited (PlayerColor player) const -{ - return false; -} -bool IObjectInterface::wasVisited (const CGHeroInstance * h) const -{ - return false; -} - -void IObjectInterface::postInit() -{} - -void IObjectInterface::preInit() -{} - -void IObjectInterface::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - -} - -void IObjectInterface::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - -} - -void IObjectInterface::garrisonDialogClosed(const CGHeroInstance *hero) const -{ - -} - -void IObjectInterface::heroLevelUpDone(const CGHeroInstance *hero) const -{ - -} - -void CPlayersVisited::setPropertyDer( ui8 what, ui32 val ) -{ - if(what == 10) - players.insert(PlayerColor(val)); -} - -bool CPlayersVisited::wasVisited( PlayerColor player ) const -{ - return vstd::contains(players,player); -} - -bool CPlayersVisited::wasVisited( TeamID team ) const -{ - for(auto i : players) - { - if(cb->getPlayer(i)->team == team) - return true; - } - return false; -} - -// Bank helper. Find the creature ID and their number, and store the -// result in storage (either guards or reward creatures). -static void readCreatures(const JsonNode &creature, std::vector< std::pair > &storage) -{ - std::pair creInfo = std::make_pair(CreatureID::NONE, 0); - - //TODO: replace numeric id's with mod-friendly string id's - creInfo.second = creature["number"].Float(); - creInfo.first = CreatureID((si32)creature["id"].Float()); - storage.push_back(creInfo); -} - -// Bank helper. Process a bank level. -static void readBankLevel(const JsonNode &level, BankConfig &bc) -{ - int idx; - - bc.chance = level["chance"].Float(); - - for(const JsonNode &creature : level["guards"].Vector()) - { - readCreatures(creature, bc.guards); - } - - bc.upgradeChance = level["upgrade_chance"].Float(); - bc.combatValue = level["combat_value"].Float(); - - bc.resources = Res::ResourceSet(level["reward_resources"]); - - for(const JsonNode &creature : level["reward_creatures"].Vector()) - { - readCreatures(creature, bc.creatures); - } - - bc.artifacts.resize(4); - idx = 0; - for(const JsonNode &artifact : level["reward_artifacts"].Vector()) - { - bc.artifacts[idx] = artifact.Float(); - idx ++; - } - - bc.value = level["value"].Float(); - bc.rewardDifficulty = level["profitability"].Float(); - bc.easiest = level["easiest"].Float(); -} - -CObjectHandler::CObjectHandler() -{ - logGlobal->traceStream() << "\t\tReading cregens "; - - const JsonNode config(ResourceID("config/dwellings.json")); - for(const JsonNode &dwelling : config["dwellings"].Vector()) - { - cregens[dwelling["dwelling"].Float()] = CreatureID((si32)dwelling["creature"].Float()); - } - logGlobal->traceStream() << "\t\tDone loading cregens!"; - - logGlobal->traceStream() << "\t\tReading resources prices "; - const JsonNode config2(ResourceID("config/resources.json")); - for(const JsonNode &price : config2["resources_prices"].Vector()) - { - resVals.push_back(price.Float()); - } - logGlobal->traceStream() << "\t\tDone loading resource prices!"; - - logGlobal->traceStream() << "\t\tReading banks configs"; - const JsonNode config3(ResourceID("config/bankconfig.json")); - int bank_num = 0; - for(const JsonNode &bank : config3["banks"].Vector()) - { - creBanksNames[bank_num] = bank["name"].String(); - - int level_num = 0; - for(const JsonNode &level : bank["levels"].Vector()) - { - banksInfo[bank_num].push_back(new BankConfig); - BankConfig &bc = *banksInfo[bank_num].back(); - bc.level = level_num; - - readBankLevel(level, bc); - level_num ++; - } - - bank_num ++; - } - logGlobal->traceStream() << "\t\tDone loading banks configs"; -} - -CObjectHandler::~CObjectHandler() -{ - for(auto & mapEntry : banksInfo) - { - for(auto & vecEntry : mapEntry.second) - { - vecEntry.dellNull(); - } - } -} - -int CObjectHandler::bankObjToIndex (const CGObjectInstance * obj) -{ - switch (obj->ID) //find appriopriate key - { - case Obj::CREATURE_BANK: - return obj->subID; - case Obj::DERELICT_SHIP: - return 8; - case Obj::DRAGON_UTOPIA: - return 10; - case Obj::CRYPT: - return 9; - case Obj::SHIPWRECK: - return 7; - case Obj::PYRAMID: - return 21; - default: - logGlobal->warnStream() << "Unrecognized Bank indetifier!"; - return 0; - } -} -PlayerColor CGObjectInstance::getOwner() const -{ - //if (state) - // return state->owner; - //else - return tempOwner; //won't have owner -} - -CGObjectInstance::CGObjectInstance(): - pos(-1,-1,-1), - ID(Obj::NO_OBJ), - subID(-1), - tempOwner(PlayerColor::UNFLAGGABLE), - blockVisit(false) -{ -} -CGObjectInstance::~CGObjectInstance() -{ - //if (state) - // delete state; - //state=nullptr; -} - -const std::string & CGObjectInstance::getHoverText() const -{ - return hoverName; -} -void CGObjectInstance::setOwner(PlayerColor ow) -{ - //if (state) - // state->owner = ow; - //else - tempOwner = ow; -} -int CGObjectInstance::getWidth() const//returns width of object graphic in tiles -{ - return appearance.getWidth(); -} -int CGObjectInstance::getHeight() const //returns height of object graphic in tiles -{ - return appearance.getHeight(); -} -bool CGObjectInstance::visitableAt(int x, int y) const //returns true if object is visitable at location (x, y) form left top tile of image (x, y in tiles) -{ - return appearance.isVisitableAt(pos.x - x, pos.y - y); -} -bool CGObjectInstance::blockingAt(int x, int y) const -{ - return appearance.isBlockedAt(pos.x - x, pos.y - y); -} - -bool CGObjectInstance::coveringAt(int x, int y) const -{ - return appearance.isVisibleAt(pos.x - x, pos.y - y); -} - -std::set CGObjectInstance::getBlockedPos() const -{ - std::set ret; - for(int w=0; w CGObjectInstance::getBlockedOffsets() const -{ - std::set ret; - for(int w=0; w cmp.appearance.printPriority; - - if(pos.y != cmp.pos.y) - return pos.y < cmp.pos.y; - - if(cmp.ID==Obj::HERO && ID!=Obj::HERO) - return true; - if(cmp.ID!=Obj::HERO && ID==Obj::HERO) - return false; - - if(!isVisitable() && cmp.isVisitable()) - return true; - if(!cmp.isVisitable() && isVisitable()) - return false; - if(this->pos.x &tiles) const //returns reference to the set -{ - cb->getTilesInRange(tiles, getSightCenter(), getSightRadious(), tempOwner, 1); -} -void CGObjectInstance::hideTiles(PlayerColor ourplayer, int radius) const -{ - for (auto i = cb->gameState()->teams.begin(); i != cb->gameState()->teams.end(); i++) - { - if ( !vstd::contains(i->second.players, ourplayer ))//another team - { - for (auto & elem : i->second.players) - if ( cb->getPlayer(elem)->status == EPlayerStatus::INGAME )//seek for living player (if any) - { - FoWChange fw; - fw.mode = 0; - fw.player = elem; - cb->getTilesInRange (fw.tiles, pos, radius, (elem), -1); - cb->sendAndApply (&fw); - break; - } - } - } -} -int3 CGObjectInstance::getVisitableOffset() const -{ - for(int y = 0; y < appearance.getHeight(); y++) - for (int x = 0; x < appearance.getWidth(); x++) - if (appearance.isVisitableAt(x, y)) - return int3(x,y,0); - - logGlobal->warnStream() << "Warning: getVisitableOffset called on non-visitable obj!"; - return int3(0,0,0); -} - -void CGObjectInstance::getNameVis( std::string &hname ) const -{ - const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); - hname = VLC->generaltexth->names[ID]; - if(h) - { - const bool visited = h->hasBonusFrom(Bonus::OBJECT,ID); - hname + " " + visitedTxt(visited); - } -} - -void CGObjectInstance::giveDummyBonus(ObjectInstanceID heroID, ui8 duration) const -{ - GiveBonus gbonus; - gbonus.bonus.type = Bonus::NONE; - gbonus.id = heroID.getNum(); - gbonus.bonus.duration = duration; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - cb->giveHeroBonus(&gbonus); -} - -void CGObjectInstance::onHeroVisit( const CGHeroInstance * h ) const -{ - switch(ID) - { - case Obj::HILL_FORT: - { - openWindow(OpenWindow::HILL_FORT_WINDOW,id.getNum(),h->id.getNum()); - } - break; - case Obj::SANCTUARY: - { - //You enter the sanctuary and immediately feel as if a great weight has been lifted off your shoulders. You feel safe here. - showInfoDialog(h,114,soundBase::GETPROTECTION); - } - break; - case Obj::TAVERN: - { - openWindow(OpenWindow::TAVERN_WINDOW,h->id.getNum(),id.getNum()); - } - break; - } -} - -ui8 CGObjectInstance::getPassableness() const -{ - return 0; -} - -int3 CGObjectInstance::visitablePos() const -{ - return pos - getVisitableOffset(); -} - -bool CGObjectInstance::isVisitable() const -{ - return appearance.isVisitable(); -} - -bool CGObjectInstance::passableFor(PlayerColor color) const -{ - return getPassableness() & 1<obj->subID == obj->subID; -} - -static int lowestSpeed(const CGHeroInstance * chi) -{ - if(!chi->Slots().size()) - { - logGlobal->errorStream() << "Error! Hero " << chi->id.getNum() << " ("<name<<") has no army!"; - return 20; - } - auto i = chi->Slots().begin(); - //TODO? should speed modifiers (eg from artifacts) affect hero movement? - int ret = (i++)->second->valOfBonuses(Bonus::STACKS_SPEED); - for (;i!=chi->Slots().end();i++) - { - ret = std::min(ret, i->second->valOfBonuses(Bonus::STACKS_SPEED)); - } - return ret; -} - -ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from) const -{ - //base move cost - unsigned ret = 100; - - //if there is road both on dest and src tiles - use road movement cost - if(dest.roadType != ERoadType::NO_ROAD && from.roadType != ERoadType::NO_ROAD) - { - int road = std::min(dest.roadType,from.roadType); //used road ID - switch(road) - { - case ERoadType::DIRT_ROAD: - ret = 75; - break; - case ERoadType::GRAVEL_ROAD: - ret = 65; - break; - case ERoadType::COBBLESTONE_ROAD: - ret = 50; - break; - default: - logGlobal->errorStream() << "Unknown road type: " << road << "... Something wrong!"; - break; - } - } - else - { - //FIXME: in H3 presence of Nomad in army will remove terrain penalty for sand. Bonus not implemented in VCMI - - // NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army. - // This is clearly bug in H3 however intended behaviour is not clear. - // Current VCMI behaviour will ignore neutrals in calculations so army in VCMI - // will always have best penalty without any influence from player-defined stacks order - - bool nativeArmy = true; - for(auto stack : stacks) - { - int nativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain; - - if (nativeTerrain != -1 && nativeTerrain != from.terType) - { - nativeArmy = false; - break; - } - } - if (!nativeArmy) - ret = VLC->heroh->terrCosts[from.terType]; - } - return ret; -} - -int3 CGHeroInstance::convertPosition(int3 src, bool toh3m) //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest -{ - if (toh3m) - { - src.x+=1; - return src; - } - else - { - src.x-=1; - return src; - } -} -int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' -{ - if (h3m) - { - return pos; - } - else - { - return convertPosition(pos,false); - } -} - -bool CGHeroInstance::canWalkOnSea() const -{ - return hasBonusOfType(Bonus::FLYING_MOVEMENT) || hasBonusOfType(Bonus::WATER_WALKING); -} - -ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const -{ - for(auto & elem : secSkills) - if(elem.first == skill) - return elem.second; - return 0; -} - -void CGHeroInstance::setSecSkillLevel(SecondarySkill which, int val, bool abs) -{ - if(getSecSkillLevel(which) == 0) - { - secSkills.push_back(std::pair(which, val)); - updateSkill(which, val); - } - else - { - for (auto & elem : secSkills) - { - if(elem.first == which) - { - if(abs) - elem.second = val; - else - elem.second += val; - - if(elem.second > 3) //workaround to avoid crashes when same sec skill is given more than once - { - logGlobal->warnStream() << "Warning: Skill " << which << " increased over limit! Decreasing to Expert."; - elem.second = 3; - } - updateSkill(which, elem.second); //when we know final value - } - } - } -} - -bool CGHeroInstance::canLearnSkill() const -{ - return secSkills.size() < GameConstants::SKILL_PER_HERO; -} - -int CGHeroInstance::maxMovePoints(bool onLand) const -{ - int base; - - if(onLand) - { - // used function is f(x) = 66.6x + 1300, rounded to second digit, where x is lowest speed in army - static const int baseSpeed = 1300; // base speed from creature with 0 speed - - int armySpeed = lowestSpeed(this) * 20 / 3; - - base = armySpeed * 10 + baseSpeed; // separate *10 is intentional to receive same rounding as in h3 - vstd::abetween(base, 1500, 2000); // base speed is limited by these values - } - else - { - base = 1500; //on water base movement is always 1500 (speed of army doesn't matter) - } - - const Bonus::BonusType bt = onLand ? Bonus::LAND_MOVEMENT : Bonus::SEA_MOVEMENT; - const int bonus = valOfBonuses(Bonus::MOVEMENT) + valOfBonuses(bt); - - const int subtype = onLand ? SecondarySkill::LOGISTICS : SecondarySkill::NAVIGATION; - const double modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0; - - return int(base* (1+modifier)) + bonus; -} - -CGHeroInstance::CGHeroInstance() - : IBoatGenerator(this) -{ - setNodeType(HERO); - ID = Obj::HERO; - tacticFormationEnabled = inTownGarrison = false; - mana = movement = portrait = level = -1; - isStanding = true; - moveDir = 4; - exp = 0xffffffff; - visitedTown = nullptr; - type = nullptr; - boat = nullptr; - commander = nullptr; - sex = 0xff; - secSkills.push_back(std::make_pair(SecondarySkill::DEFAULT, -1)); -} - -void CGHeroInstance::initHero(HeroTypeID SUBID) -{ - subID = SUBID.getNum(); - initHero(); -} - -void CGHeroInstance::initHero() -{ - assert(validTypes(true)); - if(!type) - type = VLC->heroh->heroes[subID]; - - if (ID == Obj::HERO) - appearance = VLC->dobjinfo->pickCandidates(Obj::HERO, type->heroClass->id).front(); - - if(!vstd::contains(spells, SpellID::PRESET)) //hero starts with a spell - { - for(auto spellID : type->spells) - spells.insert(spellID); - } - else //remove placeholder - spells -= SpellID::PRESET; - - if(!getArt(ArtifactPosition::MACH4) && !getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook) //no catapult means we haven't read pre-existent set -> use default rules for spellbook - putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0)); - - if(!getArt(ArtifactPosition::MACH4)) - putArtifact(ArtifactPosition::MACH4, CArtifactInstance::createNewArtifactInstance(3)); //everyone has a catapult - - if(portrait < 0 || portrait == 255) - portrait = type->imageIndex; - if(!hasBonus(Selector::sourceType(Bonus::HERO_BASE_SKILL))) - { - for(int g=0; g(g), type->heroClass->primarySkillInitial[g]); - } - } - if(secSkills.size() == 1 && secSkills[0] == std::pair(SecondarySkill::DEFAULT, -1)) //set secondary skills to default - secSkills = type->secSkillsInit; - if (!name.length()) - name = type->name; - - if (sex == 0xFF)//sex is default - sex = type->sex; - - setFormation(false); - if (!stacksCount()) //standard army//initial army - { - initArmy(); - } - assert(validTypes()); - - level = 1; - if(exp == 0xffffffff) - { - initExp(); - } - else - { - levelUpAutomatically(); - } - - if (VLC->modh->modules.COMMANDERS && !commander) - { - commander = new CCommanderInstance(type->heroClass->commander->idNumber); - commander->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders - commander->giveStackExp (exp); //after our exp is set - } - - if (mana < 0) - mana = manaLimit(); -} - -void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= nullptr*/) -{ - if(!dst) - dst = this; - - int howManyStacks = 0; //how many stacks will hero receives <1 - 3> - int pom = cb->gameState()->getRandomGenerator().nextInt(99); - int warMachinesGiven = 0; - - if(pom < 9) - howManyStacks = 1; - else if(pom < 79) - howManyStacks = 2; - else - howManyStacks = 3; - - vstd::amin(howManyStacks, type->initialArmy.size()); - - for(int stackNo=0; stackNo < howManyStacks; stackNo++) - { - auto & stack = type->initialArmy[stackNo]; - - int count = cb->gameState()->getRandomGenerator().nextInt(stack.minAmount, stack.maxAmount); - - if(stack.creature >= CreatureID::CATAPULT && - stack.creature <= CreatureID::ARROW_TOWERS) //war machine - { - warMachinesGiven++; - if(dst != this) - continue; - - int slot = -1; - ArtifactID aid = ArtifactID::NONE; - switch (stack.creature) - { - case CreatureID::CATAPULT: - slot = ArtifactPosition::MACH4; - aid = ArtifactID::CATAPULT; - break; - default: - aid = CArtHandler::creatureToMachineID(stack.creature); - slot = 9 + aid; - break; - } - auto convSlot = ArtifactPosition(slot); - if(!getArt(convSlot)) - putArtifact(convSlot, CArtifactInstance::createNewArtifactInstance(aid)); - else - logGlobal->warnStream() << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid; - } - else - dst->setCreature(SlotID(stackNo-warMachinesGiven), stack.creature, count); - } -} - -CGHeroInstance::~CGHeroInstance() -{ - commander.dellNull(); -} - -bool CGHeroInstance::needsLastStack() const -{ - return true; -} - -void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const -{ - if(h == this) return; //exclude potential self-visiting - - if (ID == Obj::HERO) - { - if( cb->gameState()->getPlayerRelations(tempOwner, h->tempOwner)) //our or ally hero - { - //exchange - cb->heroExchange(h->id, id); - } - else //battle - { - if(visitedTown) //we're in town - visitedTown->onHeroVisit(h); //town will handle attacking - else - cb->startBattleI(h, this); - } - } - else if(ID == Obj::PRISON) - { - int txt_id; - - if (cb->getHeroCount(h->tempOwner, false) < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)//GameConstants::MAX_HEROES_PER_PLAYER) //free hero slot - { - cb->changeObjPos(id,pos+int3(1,0,0),0); - //update hero parameters - SetMovePoints smp; - smp.hid = id; - smp.val = maxMovePoints (true); //TODO: hota prison on water? - cb->setMovePoints (&smp); - cb->setManaPoints (id, manaLimit()); - - cb->setObjProperty(id, ObjProperty::ID, Obj::HERO); //set ID to 34 - cb->giveHero(id,h->tempOwner); //recreates def and adds hero to player - - txt_id = 102; - } - else //already 8 wandering heroes - { - txt_id = 103; - } - - showInfoDialog(h,txt_id,soundBase::ROGUE); - } -} - -const std::string & CGHeroInstance::getHoverText() const -{ - if(ID != Obj::PRISON) - { - hoverName = VLC->generaltexth->allTexts[15]; - boost::algorithm::replace_first(hoverName,"%s",name); - boost::algorithm::replace_first(hoverName,"%s", type->heroClass->name); - return hoverName; - } - else - hoverName = VLC->generaltexth->names[ID]; - - return hoverName; -} - -const std::string & CGHeroInstance::getBiography() const -{ - if (biography.length()) - return biography; - return type->biography; -} - -ui8 CGHeroInstance::maxlevelsToMagicSchool() const -{ - return type->heroClass->isMagicHero() ? 3 : 4; -} -ui8 CGHeroInstance::maxlevelsToWisdom() const -{ - return type->heroClass->isMagicHero() ? 3 : 6; -} - -void CGHeroInstance::SecondarySkillsInfo::resetMagicSchoolCounter() -{ - magicSchoolCounter = 1; -} -void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter() -{ - wisdomCounter = 1; -} - -void CGHeroInstance::initObj() -{ - blockVisit = true; - auto hs = new HeroSpecial(); - hs->setNodeType(CBonusSystemNode::SPECIALTY); - attachTo(hs); //do we ever need to detach it? - - if(!type) - initHero(); //TODO: set up everything for prison before specialties are configured - - skillsInfo.rand.setSeed(cb->gameState()->getRandomGenerator().nextInt()); - skillsInfo.resetMagicSchoolCounter(); - skillsInfo.resetWisdomCounter(); - - for(const auto &spec : type->spec) //TODO: unfity with bonus system - { - auto bonus = new Bonus(); - bonus->val = spec.val; - bonus->sid = id.getNum(); //from the hero, specialty has no unique id - bonus->duration = Bonus::PERMANENT; - bonus->source = Bonus::HERO_SPECIAL; - switch (spec.type) - { - case 1:// creature specialty - { - hs->growsWithLevel = true; - - const CCreature &specCreature = *VLC->creh->creatures[spec.additionalinfo]; //creature in which we have specialty - - //int creLevel = specCreature.level; - //if(!creLevel) - //{ - // if(spec.additionalinfo == 146) - // creLevel = 5; //treat ballista as 5-level - // else - // { - // logGlobal->warnStream() << "Warning: unknown level of " << specCreature.namePl; - // continue; - // } - //} - - //bonus->additionalInfo = spec.additionalinfo; //creature id, should not be used again - this works only with limiter - bonus->limiter.reset(new CCreatureTypeLimiter (specCreature, true)); //with upgrades - bonus->type = Bonus::PRIMARY_SKILL; - bonus->valType = Bonus::ADDITIVE_VALUE; - - bonus->subtype = PrimarySkill::ATTACK; - hs->addNewBonus(bonus); - - bonus = new Bonus(*bonus); - bonus->subtype = PrimarySkill::DEFENSE; - hs->addNewBonus(bonus); - //values will be calculated later - - bonus = new Bonus(*bonus); - bonus->type = Bonus::STACKS_SPEED; - bonus->val = 1; //+1 speed - hs->addNewBonus(bonus); - } - break; - case 2://secondary skill - hs->growsWithLevel = true; - bonus->type = Bonus::SPECIAL_SECONDARY_SKILL; //needs to be recalculated with level, based on this value - bonus->valType = Bonus::BASE_NUMBER; // to receive nonzero value - bonus->subtype = spec.subtype; //skill id - bonus->val = spec.val; //value per level, in percent - hs->addNewBonus(bonus); - bonus = new Bonus(*bonus); - - switch (spec.additionalinfo) - { - case 0: //normal - bonus->valType = Bonus::PERCENT_TO_BASE; - break; - case 1: //when it's navigation or there's no 'base' at all - bonus->valType = Bonus::PERCENT_TO_ALL; - break; - } - bonus->type = Bonus::SECONDARY_SKILL_PREMY; //value will be calculated later - hs->addNewBonus(bonus); - break; - case 3://spell damage bonus, level dependent but calculated elsewhere - bonus->type = Bonus::SPECIAL_SPELL_LEV; - bonus->subtype = spec.subtype; - hs->addNewBonus(bonus); - break; - case 4://creature stat boost - switch (spec.subtype) - { - case 1://attack - bonus->type = Bonus::PRIMARY_SKILL; - bonus->subtype = PrimarySkill::ATTACK; - break; - case 2://defense - bonus->type = Bonus::PRIMARY_SKILL; - bonus->subtype = PrimarySkill::DEFENSE; - break; - case 3: - bonus->type = Bonus::CREATURE_DAMAGE; - bonus->subtype = 0; //both min and max - break; - case 4://hp - bonus->type = Bonus::STACK_HEALTH; - break; - case 5: - bonus->type = Bonus::STACKS_SPEED; - break; - default: - continue; - } - bonus->additionalInfo = spec.additionalinfo; //creature id - bonus->valType = Bonus::ADDITIVE_VALUE; - bonus->limiter.reset(new CCreatureTypeLimiter (*VLC->creh->creatures[spec.additionalinfo], true)); - hs->addNewBonus(bonus); - break; - case 5://spell damage bonus in percent - bonus->type = Bonus::SPECIFIC_SPELL_DAMAGE; - bonus->valType = Bonus::BASE_NUMBER; // current spell system is screwed - bonus->subtype = spec.subtype; //spell id - hs->addNewBonus(bonus); - break; - case 6://damage bonus for bless (Adela) - bonus->type = Bonus::SPECIAL_BLESS_DAMAGE; - bonus->subtype = spec.subtype; //spell id if you ever wanted to use it otherwise - bonus->additionalInfo = spec.additionalinfo; //damage factor - hs->addNewBonus(bonus); - break; - case 7://maxed mastery for spell - bonus->type = Bonus::MAXED_SPELL; - bonus->subtype = spec.subtype; //spell i - hs->addNewBonus(bonus); - break; - case 8://peculiar spells - enchantments - bonus->type = Bonus::SPECIAL_PECULIAR_ENCHANT; - bonus->subtype = spec.subtype; //spell id - bonus->additionalInfo = spec.additionalinfo;//0, 1 for Coronius - hs->addNewBonus(bonus); - break; - case 9://upgrade creatures - { - const auto &creatures = VLC->creh->creatures; - bonus->type = Bonus::SPECIAL_UPGRADE; - bonus->subtype = spec.subtype; //base id - bonus->additionalInfo = spec.additionalinfo; //target id - hs->addNewBonus(bonus); - bonus = new Bonus(*bonus); - - for(auto cre_id : creatures[spec.subtype]->upgrades) - { - bonus->subtype = cre_id; //propagate for regular upgrades of base creature - hs->addNewBonus(bonus); - bonus = new Bonus(*bonus); - } - vstd::clear_pointer(bonus); - break; - } - case 10://resource generation - bonus->type = Bonus::GENERATE_RESOURCE; - bonus->subtype = spec.subtype; - hs->addNewBonus(bonus); - break; - case 11://starting skill with mastery (Adrienne) - setSecSkillLevel(SecondarySkill(spec.val), spec.additionalinfo, true); - break; - case 12://army speed - bonus->type = Bonus::STACKS_SPEED; - hs->addNewBonus(bonus); - break; - case 13://Dragon bonuses (Mutare) - bonus->type = Bonus::PRIMARY_SKILL; - bonus->valType = Bonus::ADDITIVE_VALUE; - switch (spec.subtype) - { - case 1: - bonus->subtype = PrimarySkill::ATTACK; - break; - case 2: - bonus->subtype = PrimarySkill::DEFENSE; - break; - } - bonus->limiter.reset(new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE)); - hs->addNewBonus(bonus); - break; - default: - logGlobal->warnStream() << "Unexpected hero specialty " << type; - } - } - specialty.push_back(hs); //will it work? - - for (auto hs2 : type->specialty) //copy active (probably growing) bonuses from hero prootype to hero object - { - auto hs = new HeroSpecial(); - attachTo(hs); //do we ever need to detach it? - - hs->setNodeType(CBonusSystemNode::SPECIALTY); - for (auto bonus : hs2.bonuses) - { - hs->addNewBonus (bonus); - } - hs->growsWithLevel = hs2.growsWithLevel; - - specialty.push_back(hs); //will it work? - } - - //initialize bonuses - recreateSecondarySkillsBonuses(); - Updatespecialty(); - - mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one - type->name = name; -} -void CGHeroInstance::Updatespecialty() //TODO: calculate special value of bonuses on-the-fly? -{ - for (auto hs : specialty) - { - if (hs->growsWithLevel) - { - //const auto &creatures = VLC->creh->creatures; - - for(Bonus * b : hs->getBonusList()) - { - switch (b->type) - { - case Bonus::SECONDARY_SKILL_PREMY: - b->val = (hs->valOfBonuses(Bonus::SPECIAL_SECONDARY_SKILL, b->subtype) * level); - break; //use only hero skills as bonuses to avoid feedback loop - case Bonus::PRIMARY_SKILL: //for creatures, that is - { - const CCreature * cre = nullptr; - int creLevel = 0; - if (auto creatureLimiter = std::dynamic_pointer_cast(b->limiter)) //TODO: more general eveluation of bonuses? - { - cre = creatureLimiter->creature; - creLevel = cre->level; - if (!creLevel) - { - creLevel = 5; //treat ballista as tier 5 - } - } - else //no creature found, can't calculate value - { - logGlobal->warnStream() << "Primary skill specialty growth supported only with creature type limiters"; - break; - } - - double primSkillModifier = (int)(level / creLevel) / 20.0; - int param; - switch (b->subtype) - { - case PrimarySkill::ATTACK: - param = cre->Attack(); - break; - case PrimarySkill::DEFENSE: - param = cre->Defense(); - break; - default: - continue; - } - b->val = ceil(param * (1 + primSkillModifier)) - param; //yep, overcomplicated but matches original - break; - } - } - } - } - } -} - -void CGHeroInstance::recreateSecondarySkillsBonuses() -{ - auto secondarySkillsBonuses = getBonuses(Selector::sourceType(Bonus::SECONDARY_SKILL)); - for(auto bonus : *secondarySkillsBonuses) - removeBonus(bonus); - - for(auto skill_info : secSkills) - updateSkill(SecondarySkill(skill_info.first), skill_info.second); -} - -void CGHeroInstance::updateSkill(SecondarySkill which, int val) -{ - if(which == SecondarySkill::LEADERSHIP || which == SecondarySkill::LUCK) - { //luck-> VLC->generaltexth->arraytxt[73+luckSkill]; VLC->generaltexth->arraytxt[104+moraleSkill] - bool luck = which == SecondarySkill::LUCK; - Bonus::BonusType type[] = {Bonus::MORALE, Bonus::LUCK}; - - Bonus *b = getBonusLocalFirst(Selector::type(type[luck]).And(Selector::sourceType(Bonus::SECONDARY_SKILL))); - if(!b) - { - b = new Bonus(Bonus::PERMANENT, type[luck], Bonus::SECONDARY_SKILL, +val, which, which, Bonus::BASE_NUMBER); - addNewBonus(b); - } - else - b->val = +val; - } - else if(which == SecondarySkill::DIPLOMACY) //surrender discount: 20% per level - { - - if(Bonus *b = getBonusLocalFirst(Selector::type(Bonus::SURRENDER_DISCOUNT).And(Selector::sourceType(Bonus::SECONDARY_SKILL)))) - b->val = +val; - else - addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::SURRENDER_DISCOUNT, Bonus::SECONDARY_SKILL, val * 20, which)); - } - - int skillVal = 0; - switch (which) - { - case SecondarySkill::ARCHERY: - switch (val) - { - case 1: - skillVal = 10; break; - case 2: - skillVal = 25; break; - case 3: - skillVal = 50; break; - } - break; - case SecondarySkill::LOGISTICS: - skillVal = 10 * val; break; - case SecondarySkill::NAVIGATION: - skillVal = 50 * val; break; - case SecondarySkill::MYSTICISM: - skillVal = val; break; - case SecondarySkill::EAGLE_EYE: - skillVal = 30 + 10 * val; break; - case SecondarySkill::NECROMANCY: - skillVal = 10 * val; break; - case SecondarySkill::LEARNING: - skillVal = 5 * val; break; - case SecondarySkill::OFFENCE: - skillVal = 10 * val; break; - case SecondarySkill::ARMORER: - skillVal = 5 * val; break; - case SecondarySkill::INTELLIGENCE: - skillVal = 25 << (val-1); break; - case SecondarySkill::SORCERY: - skillVal = 5 * val; break; - case SecondarySkill::RESISTANCE: - skillVal = 5 << (val-1); break; - case SecondarySkill::FIRST_AID: - skillVal = 25 + 25*val; break; - case SecondarySkill::ESTATES: - skillVal = 125 << (val-1); break; - } - - - Bonus::ValueType skillValType = skillVal ? Bonus::BASE_NUMBER : Bonus::INDEPENDENT_MIN; - if(Bonus * b = getBonusList().getFirst(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, which) - .And(Selector::sourceType(Bonus::SECONDARY_SKILL)))) //only local hero bonus - { - b->val = skillVal; - b->valType = skillValType; - } - else - { - auto bonus = new Bonus(Bonus::PERMANENT, Bonus::SECONDARY_SKILL_PREMY, Bonus::SECONDARY_SKILL, skillVal, id.getNum(), which, skillValType); - bonus->source = Bonus::SECONDARY_SKILL; - addNewBonus(bonus); - } - -} -void CGHeroInstance::setPropertyDer( ui8 what, ui32 val ) -{ - if(what == ObjProperty::PRIMARY_STACK_COUNT) - setStackCount(SlotID(0), val); -} - -double CGHeroInstance::getFightingStrength() const -{ - return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE))); -} - -double CGHeroInstance::getMagicStrength() const -{ - return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::KNOWLEDGE)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::SPELL_POWER))); -} - -double CGHeroInstance::getHeroStrength() const -{ - return sqrt(pow(getFightingStrength(), 2.0) * pow(getMagicStrength(), 2.0)); -} - -ui64 CGHeroInstance::getTotalStrength() const -{ - double ret = getFightingStrength() * getArmyStrength(); - return (ui64) ret; -} - -TExpType CGHeroInstance::calculateXp(TExpType exp) const -{ - return exp * (100 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::LEARNING))/100.0; -} - -ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool) const -{ - si16 skill = -1; //skill level - -#define TRY_SCHOOL(schoolName, schoolMechanicsId, schoolOutId) \ - if(spell-> schoolName) \ - { \ - int thisSchool = std::max(getSecSkillLevel( \ - SecondarySkill(14 + (schoolMechanicsId))), \ - valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << (schoolMechanicsId))); \ - if(thisSchool > skill) \ - { \ - skill = thisSchool; \ - if(outSelectedSchool) \ - *outSelectedSchool = schoolOutId; \ - } \ - } - TRY_SCHOOL(fire, 0, 1) - TRY_SCHOOL(air, 1, 0) - TRY_SCHOOL(water, 2, 2) - TRY_SCHOOL(earth, 3, 3) -#undef TRY_SCHOOL - - - - vstd::amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus - vstd::amax(skill, valOfBonuses(Bonus::SPELL, spell->id.toEnum())); //given by artifact or other effect - if (hasBonusOfType(Bonus::MAXED_SPELL, spell->id))//hero specialty (Daremyth, Melodia) - skill = 3; - assert(skill >= 0 && skill <= 3); - return skill; -} - -bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const -{ - if(!getArt(ArtifactPosition::SPELLBOOK)) //if hero has no spellbook - return false; - - if (spell->isSpecialSpell()) - { - if (vstd::contains(spells, spell->id)) - {//hero has this spell in spellbook - logGlobal->errorStream() << "Special spell in spellbook "<name; - } - - if (hasBonusOfType(Bonus::SPELL, spell->id)) - return true; - - return false; - } - else - { - if(vstd::contains(spells, spell->id) //hero has this spell in spellbook - || (spell->air && hasBonusOfType(Bonus::AIR_SPELLS)) // this is air spell and hero can cast all air spells - || (spell->fire && hasBonusOfType(Bonus::FIRE_SPELLS)) // this is fire spell and hero can cast all fire spells - || (spell->water && hasBonusOfType(Bonus::WATER_SPELLS)) // this is water spell and hero can cast all water spells - || (spell->earth && hasBonusOfType(Bonus::EARTH_SPELLS)) // this is earth spell and hero can cast all earth spells - || hasBonusOfType(Bonus::SPELL, spell->id) - || hasBonusOfType(Bonus::SPELLS_OF_LEVEL, spell->level) - ) - return true; - - return false; - } -} - -/** - * Calculates what creatures and how many to be raised from a battle. - * @param battleResult The results of the battle. - * @return Returns a pair with the first value indicating the ID of the creature - * type and second value the amount. Both values are returned as -1 if necromancy - * could not be applied. - */ -CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &battleResult) const -{ - const ui8 necromancyLevel = getSecSkillLevel(SecondarySkill::NECROMANCY); - - // Hero knows necromancy or has Necromancer Cloak - if (necromancyLevel > 0 || hasBonusOfType(Bonus::IMPROVED_NECROMANCY)) - { - double necromancySkill = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::NECROMANCY)/100.0; - vstd::amin(necromancySkill, 1.0); //it's impossible to raise more creatures than all... - const std::map &casualties = battleResult.casualties[!battleResult.winner]; - ui32 raisedUnits = 0; - - // Figure out what to raise and how many. - const CreatureID creatureTypes[] = {CreatureID::SKELETON, CreatureID::WALKING_DEAD, CreatureID::WIGHTS, CreatureID::LICHES}; - const bool improvedNecromancy = hasBonusOfType(Bonus::IMPROVED_NECROMANCY); - const CCreature *raisedUnitType = VLC->creh->creatures[creatureTypes[improvedNecromancy ? necromancyLevel : 0]]; - const ui32 raisedUnitHP = raisedUnitType->valOfBonuses(Bonus::STACK_HEALTH); - - //calculate creatures raised from each defeated stack - for (auto & casualtie : casualties) - { - // Get lost enemy hit points convertible to units. - CCreature * c = VLC->creh->creatures[casualtie.first]; - - const ui32 raisedHP = c->valOfBonuses(Bonus::STACK_HEALTH) * casualtie.second * necromancySkill; - raisedUnits += std::min(raisedHP / raisedUnitHP, casualtie.second * necromancySkill); //limit to % of HP and % of original stack count - } - - // Make room for new units. - SlotID slot = getSlotFor(raisedUnitType->idNumber); - if (slot == SlotID()) - { - // If there's no room for unit, try it's upgraded version 2/3rds the size. - raisedUnitType = VLC->creh->creatures[*raisedUnitType->upgrades.begin()]; - raisedUnits = (raisedUnits*2)/3; - - slot = getSlotFor(raisedUnitType->idNumber); - } - if (raisedUnits <= 0) - raisedUnits = 1; - - return CStackBasicDescriptor(raisedUnitType->idNumber, raisedUnits); - } - - return CStackBasicDescriptor(); -} - -/** - * Show the necromancy dialog with information about units raised. - * @param raisedStack Pair where the first element represents ID of the raised creature - * and the second element the amount. - */ -void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const -{ - InfoWindow iw; - iw.soundID = soundBase::pickup01 + cb->gameState()->getRandomGenerator().nextInt(6); - iw.player = tempOwner; - iw.components.push_back(Component(raisedStack)); - - if (raisedStack.count > 1) // Practicing the dark arts of necromancy, ... (plural) - { - iw.text.addTxt(MetaString::GENERAL_TXT, 145); - iw.text.addReplacement(raisedStack.count); - } - else // Practicing the dark arts of necromancy, ... (singular) - { - iw.text.addTxt(MetaString::GENERAL_TXT, 146); - } - iw.text.addReplacement(raisedStack); - - cb->showInfoDialog(&iw); -} - -int3 CGHeroInstance::getSightCenter() const -{ - return getPosition(false); -} - -int CGHeroInstance::getSightRadious() const -{ - return 5 + getSecSkillLevel(SecondarySkill::SCOUTING) + valOfBonuses(Bonus::SIGHT_RADIOUS); //default + scouting -} - -si32 CGHeroInstance::manaRegain() const -{ - if (hasBonusOfType(Bonus::FULL_MANA_REGENERATION)) - return manaLimit(); - - return 1 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 8) + valOfBonuses(Bonus::MANA_REGENERATION); //1 + Mysticism level -} - -// /** -// * Places an artifact in hero's backpack. If it's a big artifact equips it -// * or discards it if it cannot be equipped. -// */ -// void CGHeroInstance::giveArtifact (ui32 aid) //use only for fixed artifacts -// { -// CArtifact * const artifact = VLC->arth->artifacts[aid]; //pointer to constant object -// CArtifactInstance *ai = CArtifactInstance::createNewArtifactInstance(artifact); -// ai->putAt(this, ai->firstAvailableSlot(this)); -// } - -int CGHeroInstance::getBoatType() const -{ - switch(type->heroClass->getAlignment()) - { - case EAlignment::GOOD: - return 1; - case EAlignment::EVIL: - return 0; - case EAlignment::NEUTRAL: - return 2; - default: - throw std::runtime_error("Wrong alignment!"); - } -} - -void CGHeroInstance::getOutOffsets(std::vector &offsets) const -{ - static int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) }; - for (auto & dir : dirs) - offsets += dir; -} - -int CGHeroInstance::getSpellCost(const CSpell *sp) const -{ - return sp->getCost(getSpellSchoolLevel(sp)); -} - -void CGHeroInstance::pushPrimSkill( PrimarySkill::PrimarySkill which, int val ) -{ - assert(!hasBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, which) - .And(Selector::sourceType(Bonus::HERO_BASE_SKILL)))); - addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::PRIMARY_SKILL, Bonus::HERO_BASE_SKILL, val, id.getNum(), which)); -} - -EAlignment::EAlignment CGHeroInstance::getAlignment() const -{ - return type->heroClass->getAlignment(); -} - -void CGHeroInstance::initExp() -{ - exp = cb->gameState()->getRandomGenerator().nextInt(40, 89); -} - -std::string CGHeroInstance::nodeName() const -{ - return "Hero " + name; -} - -void CGHeroInstance::putArtifact(ArtifactPosition pos, CArtifactInstance *art) -{ - assert(!getArt(pos)); - art->putAt(ArtifactLocation(this, pos)); -} - -void CGHeroInstance::putInBackpack(CArtifactInstance *art) -{ - putArtifact(art->firstBackpackSlot(this), art); -} - -bool CGHeroInstance::hasSpellbook() const -{ - return getArt(ArtifactPosition::SPELLBOOK); -} - -void CGHeroInstance::deserializationFix() -{ - artDeserializationFix(this); - - for (auto hs : specialty) - { - attachTo (hs); - } -} - -CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs) -{ - if(visitedTown) - { - if(inTownGarrison) - return visitedTown; - else - return &visitedTown->townAndVis; - } - else - return CArmedInstance::whereShouldBeAttached(gs); -} - -int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/) const -{ - if(hasBonusOfType(Bonus::FREE_SHIP_BOARDING)) - return (MPsBefore - basicCost) * static_cast(maxMovePoints(disembark)) / maxMovePoints(!disembark); - - return 0; //take all MPs otherwise -} - -CGHeroInstance::ECanDig CGHeroInstance::diggingStatus() const -{ - if(movement < maxMovePoints(true)) - return LACK_OF_MOVEMENT; - else if(cb->getTile(getPosition(false))->terType == ETerrainType::WATER) - return WRONG_TERRAIN; - else - { - const TerrainTile *t = cb->getTile(getPosition()); - //TODO look for hole - //CGI->mh->getTerrainDescr(h->getPosition(false), hlp, false); - if(/*hlp.length() || */t->blockingObjects.size() > 1) - return TILE_OCCUPIED; - else - return CAN_DIG; - } -} - -ArtBearer::ArtBearer CGHeroInstance::bearerType() const -{ - return ArtBearer::HERO; -} - -std::vector CGHeroInstance::getLevelUpProposedSecondarySkills() const -{ - std::vector obligatorySkills; //hero is offered magic school or wisdom if possible - if (!skillsInfo.wisdomCounter) - { - if (cb->isAllowed(2, SecondarySkill::WISDOM) && !getSecSkillLevel(SecondarySkill::WISDOM)) - obligatorySkills.push_back(SecondarySkill::WISDOM); - } - if (!skillsInfo.magicSchoolCounter) - { - std::vector ss; - ss += SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC; - - std::shuffle(ss.begin(), ss.end(), skillsInfo.rand.getStdGenerator()); - - for (auto skill : ss) - { - if (cb->isAllowed(2, skill) && !getSecSkillLevel(skill)) //only schools hero doesn't know yet - { - obligatorySkills.push_back(skill); - break; //only one - } - } - } - - std::vector skills; - //picking sec. skills for choice - std::set basicAndAdv, expert, none; - for(int i=0;iisAllowed(2,i)) - none.insert(SecondarySkill(i)); - - for(auto & elem : secSkills) - { - if(elem.second < SecSkillLevel::EXPERT) - basicAndAdv.insert(elem.first); - else - expert.insert(elem.first); - none.erase(elem.first); - } - for (auto s : obligatorySkills) //don't duplicate them - { - none.erase (s); - basicAndAdv.erase (s); - expert.erase (s); - } - - //first offered skill: - // 1) give obligatory skill - // 2) give any other new skill - // 3) upgrade existing - if (canLearnSkill() && obligatorySkills.size() > 0) - { - skills.push_back (obligatorySkills[0]); - } - else if(none.size() && canLearnSkill()) //hero have free skill slot - { - skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //new skill - none.erase(skills.back()); - } - else if(!basicAndAdv.empty()) - { - skills.push_back(type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand)); //upgrade existing - basicAndAdv.erase(skills.back()); - } - - //second offered skill: - //1) upgrade existing - //2) give obligatory skill - //3) give any other new skill - if(!basicAndAdv.empty()) - { - SecondarySkill s = type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand);//upgrade existing - skills.push_back(s); - basicAndAdv.erase(s); - } - else if (canLearnSkill() && obligatorySkills.size() > 1) - { - skills.push_back (obligatorySkills[1]); - } - else if(none.size() && canLearnSkill()) - { - skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //give new skill - none.erase(skills.back()); - } - - return skills; -} - -PrimarySkill::PrimarySkill CGHeroInstance::nextPrimarySkill() const -{ - assert(gainsLevel()); - int randomValue = cb->gameState()->getRandomGenerator().nextInt(99), pom = 0, primarySkill = 0; - const auto & skillChances = (level > 9) ? type->heroClass->primarySkillLowLevel : type->heroClass->primarySkillHighLevel; - - for(; primarySkill < GameConstants::PRIMARY_SKILLS; ++primarySkill) - { - pom += skillChances[primarySkill]; - if(randomValue < pom) - { - break; - } - } - - logGlobal->traceStream() << "The hero gets the primary skill " << primarySkill << " with a probability of " << randomValue << "%."; - return static_cast(primarySkill); -} - -boost::optional CGHeroInstance::nextSecondarySkill() const -{ - assert(gainsLevel()); - - boost::optional chosenSecondarySkill; - const auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(); - if(!proposedSecondarySkills.empty()) - { - std::vector learnedSecondarySkills; - for(auto secondarySkill : proposedSecondarySkills) - { - if(getSecSkillLevel(secondarySkill) > 0) - { - learnedSecondarySkills.push_back(secondarySkill); - } - } - - auto & rand = cb->gameState()->getRandomGenerator(); - if(learnedSecondarySkills.empty()) - { - // there are only new skills to learn, so choose anyone of them - chosenSecondarySkill = *RandomGeneratorUtil::nextItem(proposedSecondarySkills, rand); - } - else - { - // preferably upgrade a already learned secondary skill - chosenSecondarySkill = *RandomGeneratorUtil::nextItem(learnedSecondarySkills, rand); - } - } - return chosenSecondarySkill; -} - -void CGHeroInstance::setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs) -{ - if(primarySkill < PrimarySkill::EXPERIENCE) - { - Bonus * skill = getBonusLocalFirst(Selector::type(Bonus::PRIMARY_SKILL) - .And(Selector::subtype(primarySkill)) - .And(Selector::sourceType(Bonus::HERO_BASE_SKILL))); - assert(skill); - - if(abs) - { - skill->val = value; - } - else - { - skill->val += value; - } - } - else if(primarySkill == PrimarySkill::EXPERIENCE) - { - if(abs) - { - exp = value; - } - else - { - exp += value; - } - } -} - -bool CGHeroInstance::gainsLevel() const -{ - return exp >= VLC->heroh->reqExp(level+1); -} - -void CGHeroInstance::levelUp(std::vector skills) -{ - ++level; - - //deterministic secondary skills - skillsInfo.magicSchoolCounter = (skillsInfo.magicSchoolCounter + 1) % maxlevelsToMagicSchool(); - skillsInfo.wisdomCounter = (skillsInfo.wisdomCounter + 1) % maxlevelsToWisdom(); - if(vstd::contains(skills, SecondarySkill::WISDOM)) - { - skillsInfo.resetWisdomCounter(); - } - - SecondarySkill spellSchools[] = { - SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC}; - for(auto skill : spellSchools) - { - if(vstd::contains(skills, skill)) - { - skillsInfo.resetMagicSchoolCounter(); - break; - } - } - - //specialty - Updatespecialty(); -} - -void CGHeroInstance::levelUpAutomatically() -{ - while(gainsLevel()) - { - const auto primarySkill = nextPrimarySkill(); - setPrimarySkill(primarySkill, 1, false); - - auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(); - - const auto secondarySkill = nextSecondarySkill(); - if(secondarySkill) - { - setSecSkillLevel(*secondarySkill, 1, false); - } - - //TODO why has the secondary skills to be passed to the method? - levelUp(proposedSecondarySkills); - } -} - -void CGDwelling::initObj() -{ - switch(ID) - { - case Obj::CREATURE_GENERATOR1: - { - CreatureID crid = VLC->objh->cregens[subID]; - const CCreature *crs = VLC->creh->creatures[crid]; - - creatures.resize(1); - creatures[0].second.push_back(crid); - if (subID >= VLC->generaltexth->creGens.size()) //very messy workaround - { - auto & dwellingNames = VLC->townh->factions[crs->faction]->town->dwellingNames; - assert (dwellingNames.size() > crs->level - 1); - hoverName = dwellingNames[crs->level - 1]; - } - else - hoverName = VLC->generaltexth->creGens[subID]; - if(crs->level > 4) - putStack(SlotID(0), new CStackInstance(crs, (crs->growth) * 3)); - if (getOwner() != PlayerColor::NEUTRAL) - cb->gameState()->players[getOwner()].dwellings.push_back (this); - } - break; - - case Obj::CREATURE_GENERATOR4: - creatures.resize(4); - if(subID == 1) //Golem Factory - { - creatures[0].second.push_back(CreatureID::STONE_GOLEM); - creatures[1].second.push_back(CreatureID::IRON_GOLEM); - creatures[2].second.push_back(CreatureID::GOLD_GOLEM); - creatures[3].second.push_back(CreatureID::DIAMOND_GOLEM); - //guards - putStack(SlotID(0), new CStackInstance(CreatureID::GOLD_GOLEM, 9)); - putStack(SlotID(1), new CStackInstance(CreatureID::DIAMOND_GOLEM, 6)); - } - else if(subID == 0) // Elemental Conflux - { - creatures[0].second.push_back(CreatureID::AIR_ELEMENTAL); - creatures[1].second.push_back(CreatureID::FIRE_ELEMENTAL); - creatures[2].second.push_back(CreatureID::EARTH_ELEMENTAL); - creatures[3].second.push_back(CreatureID::WATER_ELEMENTAL); - //guards - putStack(SlotID(0), new CStackInstance(CreatureID::EARTH_ELEMENTAL, 12)); - } - else - { - assert(0); - } - hoverName = VLC->generaltexth->creGens4[subID]; - break; - - case Obj::REFUGEE_CAMP: - //is handled within newturn func - break; - - case Obj::WAR_MACHINE_FACTORY: - creatures.resize(3); - creatures[0].second.push_back(CreatureID::BALLISTA); - creatures[1].second.push_back(CreatureID::FIRST_AID_TENT); - creatures[2].second.push_back(CreatureID::AMMO_CART); - break; - - default: - assert(0); - break; - } -} - -void CGDwelling::setProperty(ui8 what, ui32 val) -{ - switch (what) - { - case ObjProperty::OWNER: //change owner - if (ID == Obj::CREATURE_GENERATOR1) //single generators - { - if (tempOwner != PlayerColor::NEUTRAL) - { - std::vector >* dwellings = &cb->gameState()->players[tempOwner].dwellings; - dwellings->erase (std::find(dwellings->begin(), dwellings->end(), this)); - } - if (PlayerColor(val) != PlayerColor::NEUTRAL) //can new owner be neutral? - cb->gameState()->players[PlayerColor(val)].dwellings.push_back (this); - } - break; - case ObjProperty::AVAILABLE_CREATURE: - creatures.resize(1); - creatures[0].second.resize(1); - creatures[0].second[0] = CreatureID(val); - break; - } - CGObjectInstance::setProperty(what,val); -} -void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const -{ - if(ID == Obj::REFUGEE_CAMP && !creatures[0].first) //Refugee Camp, no available cres - { - InfoWindow iw; - iw.player = h->tempOwner; - iw.text.addTxt(MetaString::ADVOB_TXT, 44); //{%s} \n\n The camp is deserted. Perhaps you should try next week. - iw.text.addReplacement(MetaString::OBJ_NAMES, ID); - cb->sendAndApply(&iw); - return; - } - - PlayerRelations::PlayerRelations relations = cb->gameState()->getPlayerRelations( h->tempOwner, tempOwner ); - - if ( relations == PlayerRelations::ALLIES ) - return;//do not allow recruiting or capturing - - if( !relations && stacksCount() > 0) //object is guarded, owned by enemy - { - BlockingDialog bd(true,false); - bd.player = h->tempOwner; - bd.text.addTxt(MetaString::GENERAL_TXT, 421); //Much to your dismay, the %s is guarded by %s %s. Do you wish to fight the guards? - bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); - bd.text.addReplacement(MetaString::ARRAY_TXT, 176 + Slots().begin()->second->getQuantityID()*3); - bd.text.addReplacement(*Slots().begin()->second); - cb->showBlockingDialog(&bd); - return; - } - - if(!relations && ID != Obj::WAR_MACHINE_FACTORY) - { - cb->setOwner(this, h->tempOwner); - } - - BlockingDialog bd (true,false); - bd.player = h->tempOwner; - if(ID == Obj::CREATURE_GENERATOR1 || ID == Obj::CREATURE_GENERATOR4) - { - bd.text.addTxt(MetaString::ADVOB_TXT, ID == Obj::CREATURE_GENERATOR1 ? 35 : 36); //{%s} Would you like to recruit %s? / {%s} Would you like to recruit %s, %s, %s, or %s? - bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); - for(auto & elem : creatures) - bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]); - } - else if(ID == Obj::REFUGEE_CAMP) - { - bd.text.addTxt(MetaString::ADVOB_TXT, 35); //{%s} Would you like to recruit %s? - bd.text.addReplacement(MetaString::OBJ_NAMES, ID); - for(auto & elem : creatures) - bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]); - } - else if(ID == Obj::WAR_MACHINE_FACTORY) - bd.text.addTxt(MetaString::ADVOB_TXT, 157); //{War Machine Factory} Would you like to purchase War Machines? - else - throw std::runtime_error("Illegal dwelling!"); - - cb->showBlockingDialog(&bd); -} - -void CGDwelling::newTurn() const -{ - if(cb->getDate(Date::DAY_OF_WEEK) != 1) //not first day of week - return; - - //town growths and War Machines Factories are handled separately - if(ID == Obj::TOWN || ID == Obj::WAR_MACHINE_FACTORY) - return; - - if(ID == Obj::REFUGEE_CAMP) //if it's a refugee camp, we need to pick an available creature - { - cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster(cb->gameState()->getRandomGenerator())); - } - - bool change = false; - - SetAvailableCreatures sac; - sac.creatures = creatures; - sac.tid = id; - for (size_t i = 0; i < creatures.size(); i++) - { - if(creatures[i].second.size()) - { - CCreature *cre = VLC->creh->creatures[creatures[i].second[0]]; - TQuantity amount = cre->growth * (1 + cre->valOfBonuses(Bonus::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(Bonus::CREATURE_GROWTH); - if (VLC->modh->settings.DWELLINGS_ACCUMULATE_CREATURES && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures - sac.creatures[i].first += amount; - else - sac.creatures[i].first = amount; - change = true; - } - } - - if(change) - cb->sendAndApply(&sac); -} - -void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const -{ - CreatureID crid = creatures[0].second[0]; - CCreature *crs = VLC->creh->creatures[crid]; - TQuantity count = creatures[0].first; - - if(crs->level == 1 && ID != Obj::REFUGEE_CAMP) //first level - creatures are for free - { - if(count) //there are available creatures - { - SlotID slot = h->getSlotFor(crid); - if(!slot.validSlot()) //no available slot - { - InfoWindow iw; - iw.player = h->tempOwner; - iw.text.addTxt(MetaString::GENERAL_TXT, 425);//The %s would join your hero, but there aren't enough provisions to support them. - iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); - cb->showInfoDialog(&iw); - } - else //give creatures - { - SetAvailableCreatures sac; - sac.tid = id; - sac.creatures = creatures; - sac.creatures[0].first = 0; - - - InfoWindow iw; - iw.player = h->tempOwner; - iw.text.addTxt(MetaString::GENERAL_TXT, 423); //%d %s join your army. - iw.text.addReplacement(count); - iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); - - cb->showInfoDialog(&iw); - cb->sendAndApply(&sac); - cb->addToSlot(StackLocation(h, slot), crs, count); - } - } - else //there no creatures - { - InfoWindow iw; - iw.text.addTxt(MetaString::GENERAL_TXT, 422); //There are no %s here to recruit. - iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); - iw.player = h->tempOwner; - cb->sendAndApply(&iw); - } - } - else - { - if(ID == Obj::WAR_MACHINE_FACTORY) //pick available War Machines - { - //there is 1 war machine available to recruit if hero doesn't have one - SetAvailableCreatures sac; - sac.tid = id; - sac.creatures = creatures; - sac.creatures[0].first = !h->getArt(ArtifactPosition::MACH1); //ballista - sac.creatures[1].first = !h->getArt(ArtifactPosition::MACH3); //first aid tent - sac.creatures[2].first = !h->getArt(ArtifactPosition::MACH2); //ammo cart - cb->sendAndApply(&sac); - } - - OpenWindow ow; - ow.id1 = id.getNum(); - ow.id2 = h->id.getNum(); - ow.window = (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::REFUGEE_CAMP) - ? OpenWindow::RECRUITMENT_FIRST - : OpenWindow::RECRUITMENT_ALL; - cb->sendAndApply(&ow); - } -} - -void CGDwelling::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if (result.winner == 0) - { - onHeroVisit(hero); - } -} - -void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - auto relations = cb->getPlayerRelations(getOwner(), hero->getOwner()); - if(stacksCount() > 0 && relations == PlayerRelations::ENEMIES) //guards present - { - if(answer) - cb->startBattleI(hero, this); - } - else if(answer) - { - heroAcceptsCreatures(hero); - } -} - -int CGTownInstance::getSightRadious() const //returns sight distance -{ - if (subID == ETownType::TOWER) - { - if (hasBuilt(BuildingID::GRAIL)) //skyship - return -1; //entire map - if (hasBuilt(BuildingID::LOOKOUT_TOWER)) //lookout tower - return 20; - } - return 5; -} - -void CGTownInstance::setPropertyDer(ui8 what, ui32 val) -{ -///this is freakin' overcomplicated solution - switch (what) - { - case ObjProperty::STRUCTURE_ADD_VISITING_HERO: - bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, visitingHero->id.getNum()); - break; - case ObjProperty::STRUCTURE_CLEAR_VISITORS: - bonusingBuildings[val]->setProperty (ObjProperty::STRUCTURE_CLEAR_VISITORS, 0); - break; - case ObjProperty::STRUCTURE_ADD_GARRISONED_HERO: //add garrisoned hero to visitors - bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, garrisonHero->id.getNum()); - break; - case ObjProperty::BONUS_VALUE_FIRST: - bonusValue.first = val; - break; - case ObjProperty::BONUS_VALUE_SECOND: - bonusValue.second = val; - break; - } -} -CGTownInstance::EFortLevel CGTownInstance::fortLevel() const //0 - none, 1 - fort, 2 - citadel, 3 - castle -{ - if (hasBuilt(BuildingID::CASTLE)) - return CASTLE; - if (hasBuilt(BuildingID::CITADEL)) - return CITADEL; - if (hasBuilt(BuildingID::FORT)) - return FORT; - return NONE; -} - -int CGTownInstance::hallLevel() const // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol -{ - - if (hasBuilt(BuildingID::CAPITOL)) - return 3; - if (hasBuilt(BuildingID::CITY_HALL)) - return 2; - if (hasBuilt(BuildingID::TOWN_HALL)) - return 1; - if (hasBuilt(BuildingID::VILLAGE_HALL)) - return 0; - return -1; -} -int CGTownInstance::mageGuildLevel() const -{ - if (hasBuilt(BuildingID::MAGES_GUILD_5)) - return 5; - if (hasBuilt(BuildingID::MAGES_GUILD_4)) - return 4; - if (hasBuilt(BuildingID::MAGES_GUILD_3)) - return 3; - if (hasBuilt(BuildingID::MAGES_GUILD_2)) - return 2; - if (hasBuilt(BuildingID::MAGES_GUILD_1)) - return 1; - return 0; -} - -int CGTownInstance::getHordeLevel(const int & HID) const//HID - 0 or 1; returns creature level or -1 if that horde structure is not present -{ - return town->hordeLvl.at(HID); -} - -int CGTownInstance::creatureGrowth(const int & level) const -{ - return getGrowthInfo(level).totalGrowth(); -} - -GrowthInfo CGTownInstance::getGrowthInfo(int level) const -{ - GrowthInfo ret; - - if (level<0 || level >=GameConstants::CREATURES_PER_TOWN) - return ret; - if (creatures[level].second.empty()) - return ret; //no dwelling - - const CCreature *creature = VLC->creh->creatures[creatures[level].second.back()]; - const int base = creature->growth; - int castleBonus = 0; - - ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[590], base));// \n\nBasic growth %d" - - if (hasBuilt(BuildingID::CASTLE)) - ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::CASTLE, castleBonus = base)); - else if (hasBuilt(BuildingID::CITADEL)) - ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::CITADEL, castleBonus = base / 2)); - - if(town->hordeLvl.at(0) == level)//horde 1 - if(hasBuilt(BuildingID::HORDE_1)) - ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_1, creature->hordeGrowth)); - - if(town->hordeLvl.at(1) == level)//horde 2 - if(hasBuilt(BuildingID::HORDE_2)) - ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_2, creature->hordeGrowth)); - - int dwellingBonus = 0; - if(const PlayerState *p = cb->getPlayer(tempOwner, false)) - { - for(const CGDwelling *dwelling : p->dwellings) - if(vstd::contains(creatures[level].second, dwelling->creatures[0].second[0])) - dwellingBonus++; - } - - if(dwellingBonus) - ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[591], dwellingBonus));// \nExternal dwellings %+d - - //other *-of-legion-like bonuses (%d to growth cumulative with grail) - TBonusListPtr bonuses = getBonuses(Selector::type(Bonus::CREATURE_GROWTH).And(Selector::subtype(level))); - for(const Bonus *b : *bonuses) - ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val)); - - //statue-of-legion-like bonus: % to base+castle - TBonusListPtr bonuses2 = getBonuses(Selector::type(Bonus::CREATURE_GROWTH_PERCENT)); - for(const Bonus *b : *bonuses2) - ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val * (base + castleBonus) / 100)); - - if(hasBuilt(BuildingID::GRAIL)) //grail - +50% to ALL (so far added) growth - ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::GRAIL, ret.totalGrowth() / 2)); - - return ret; -} - -TResources CGTownInstance::dailyIncome() const -{ - TResources ret; - - for (auto & p : town->buildings) - { - BuildingID buildingUpgrade; - - for (auto & p2 : town->buildings) - { - if (p2.second->upgrade == p.first) - { - buildingUpgrade = p2.first; - } - } - - if (!hasBuilt(buildingUpgrade)&&(hasBuilt(p.first))) - { - ret += p.second->produce; - } - - } - - return ret; -} -bool CGTownInstance::hasFort() const -{ - return hasBuilt(BuildingID::FORT); -} -bool CGTownInstance::hasCapitol() const -{ - return hasBuilt(BuildingID::CAPITOL); -} -CGTownInstance::CGTownInstance() - :IShipyard(this), IMarket(this), town(nullptr), builded(0), destroyed(0), identifier(0), alignment(0xff) -{ - -} - -CGTownInstance::~CGTownInstance() -{ - for (auto & elem : bonusingBuildings) - delete elem; -} - -int CGTownInstance::spellsAtLevel(int level, bool checkGuild) const -{ - if(checkGuild && mageGuildLevel() < level) - return 0; - int ret = 6 - level; //how many spells are available at this level - - if (hasBuilt(BuildingID::LIBRARY, ETownType::TOWER)) - ret++; - - return ret; -} - -bool CGTownInstance::needsLastStack() const -{ - if(garrisonHero) - return true; - else return false; -} - -void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const -{ - if( !cb->gameState()->getPlayerRelations( getOwner(), h->getOwner() ))//if this is enemy - { - if(armedGarrison() || visitingHero) - { - const CGHeroInstance *defendingHero = nullptr; - const CArmedInstance *defendingArmy = this; - - if(visitingHero) - defendingHero = visitingHero; - else if(garrisonHero) - defendingHero = garrisonHero; - - if(defendingHero) - defendingArmy = defendingHero; - - bool outsideTown = (defendingHero == visitingHero && garrisonHero); - - //TODO - //"borrowing" army from garrison to visiting hero - - cb->startBattlePrimary(h, defendingArmy, getSightCenter(), h, defendingHero, false, (outsideTown ? nullptr : this)); - } - else - { - cb->setOwner(this, h->tempOwner); - removeCapitols(h->getOwner()); - cb->heroVisitCastle(this, h); - } - } - else if(h->visitablePos() == visitablePos()) - { - if (h->commander && !h->commander->alive) //rise commander. TODO: interactive script - { - SetCommanderProperty scp; - scp.heroid = h->id; - scp.which = SetCommanderProperty::ALIVE; - scp.amount = 1; - cb->sendAndApply (&scp); - } - cb->heroVisitCastle(this, h); - } - else - { - logGlobal->errorStream() << h->name << " visits allied town of " << name << " from different pos?"; - } -} - -void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const -{ - cb->stopHeroVisitCastle(this, h); -} - -void CGTownInstance::initObj() -///initialize town structures -{ - blockVisit = true; - hoverName = name + ", " + town->faction->name; - - if (subID == ETownType::DUNGEON) - creatures.resize(GameConstants::CREATURES_PER_TOWN+1);//extra dwelling for Dungeon - else - creatures.resize(GameConstants::CREATURES_PER_TOWN); - for (int level = 0; level < GameConstants::CREATURES_PER_TOWN; level++) - { - BuildingID buildID = BuildingID(BuildingID::DWELL_FIRST).advance(level); - int upgradeNum = 0; - - for (; town->buildings.count(buildID); upgradeNum++, buildID.advance(GameConstants::CREATURES_PER_TOWN)) - { - if (hasBuilt(buildID) && town->creatures.at(level).size() > upgradeNum) - creatures[level].second.push_back(town->creatures[level][upgradeNum]); - } - } - - switch (subID) - { //add new visitable objects - case 0: - bonusingBuildings.push_back (new COPWBonus(BuildingID::STABLES, this)); - break; - case 5: - bonusingBuildings.push_back (new COPWBonus(BuildingID::MANA_VORTEX, this)); - //fallthrough - case 2: case 3: case 6: - bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_4, this)); - break; - case 7: - bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_1, this)); - break; - } - //add special bonuses from buildings - - recreateBuildingsBonuses(); -} - -void CGTownInstance::newTurn() const -{ - if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week - { - auto & rand = cb->gameState()->getRandomGenerator(); - - //give resources for Rampart, Mystic Pond - if (hasBuilt(BuildingID::MYSTIC_POND, ETownType::RAMPART) - && cb->getDate(Date::DAY) != 1 && (tempOwner < PlayerColor::PLAYER_LIMIT)) - { - int resID = rand.nextInt(2, 5); //bonus to random rare resource - resID = (resID==2)?1:resID; - int resVal = rand.nextInt(1, 4);//with size 1..4 - cb->giveResource(tempOwner, static_cast(resID), resVal); - cb->setObjProperty (id, ObjProperty::BONUS_VALUE_FIRST, resID); - cb->setObjProperty (id, ObjProperty::BONUS_VALUE_SECOND, resVal); - } - - if ( subID == ETownType::DUNGEON ) - for (auto & elem : bonusingBuildings) - { - if ((elem)->ID == BuildingID::MANA_VORTEX) - cb->setObjProperty (id, ObjProperty::STRUCTURE_CLEAR_VISITORS, (elem)->id); //reset visitors for Mana Vortex - } - - if (tempOwner == PlayerColor::NEUTRAL) //garrison growth for neutral towns - { - std::vector nativeCrits; //slots - for (auto & elem : Slots()) - { - if (elem.second->type->faction == subID) //native - { - nativeCrits.push_back(elem.first); //collect matching slots - } - } - if (nativeCrits.size()) - { - SlotID pos = *RandomGeneratorUtil::nextItem(nativeCrits, rand); - StackLocation sl(this, pos); - - const CCreature *c = getCreature(pos); - if (rand.nextInt(99) < 90 || c->upgrades.empty()) //increase number if no upgrade available - { - cb->changeStackCount(sl, c->growth); - } - else //upgrade - { - cb->changeStackType(sl, VLC->creh->creatures[*c->upgrades.begin()]); - } - } - if ((stacksCount() < GameConstants::ARMY_SIZE && rand.nextInt(99) < 25) || Slots().empty()) //add new stack - { - int i = rand.nextInt(std::min(GameConstants::CREATURES_PER_TOWN, cb->getDate(Date::MONTH) << 1) - 1); - if (!town->creatures[i].empty()) - { - CreatureID c = town->creatures[i][0]; - SlotID n; - - TQuantity count = creatureGrowth(i); - if (!count) // no dwelling - count = VLC->creh->creatures[c]->growth; - - {//no lower tiers or above current month - - if ((n = getSlotFor(c)).validSlot()) - { - StackLocation sl(this, n); - if (slotEmpty(n)) - cb->insertNewStack(sl, VLC->creh->creatures[c], count); - else //add to existing - cb->changeStackCount(sl, count); - } - } - } - } - } - } -} - -int3 CGTownInstance::getSightCenter() const -{ - return pos - int3(2,0,0); -} - -ui8 CGTownInstance::getPassableness() const -{ - if (!armedGarrison())//empty castle - anyone can visit - return GameConstants::ALL_PLAYERS; - if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit - return 0; - - ui8 mask = 0; - TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner); - for(PlayerColor it : ts->players) - mask |= 1< &offsets ) const -{ - offsets += int3(-1,2,0), int3(-3,2,0); -} - -void CGTownInstance::removeCapitols (PlayerColor owner) const -{ - if (hasCapitol()) // search if there's an older capitol - { - PlayerState* state = cb->gameState()->getPlayer (owner); //get all towns owned by player - for (auto i = state->towns.cbegin(); i < state->towns.cend(); ++i) - { - if (*i != this && (*i)->hasCapitol()) - { - RazeStructures rs; - rs.tid = id; - rs.bid.insert(BuildingID::CAPITOL); - rs.destroyed = destroyed; - cb->sendAndApply(&rs); - return; - } - } - } -} - -int CGTownInstance::getBoatType() const -{ - switch (town->faction->alignment) - { - case EAlignment::EVIL : return 0; - case EAlignment::GOOD : return 1; - case EAlignment::NEUTRAL : return 2; - } - assert(0); - return -1; -} - -int CGTownInstance::getMarketEfficiency() const -{ - if (!hasBuilt(BuildingID::MARKETPLACE)) - return 0; - - const PlayerState *p = cb->getPlayer(tempOwner); - assert(p); - - int marketCount = 0; - for(const CGTownInstance *t : p->towns) - if(t->hasBuilt(BuildingID::MARKETPLACE)) - marketCount++; - - return marketCount; -} - -bool CGTownInstance::allowsTrade(EMarketMode::EMarketMode mode) const -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::RESOURCE_PLAYER: - return hasBuilt(BuildingID::MARKETPLACE); - - case EMarketMode::ARTIFACT_RESOURCE: - case EMarketMode::RESOURCE_ARTIFACT: - return hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::TOWER) - || hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::DUNGEON) - || hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::CONFLUX); - - case EMarketMode::CREATURE_RESOURCE: - return hasBuilt(BuildingID::FREELANCERS_GUILD, ETownType::STRONGHOLD); - - case EMarketMode::CREATURE_UNDEAD: - return hasBuilt(BuildingID::SKELETON_TRANSFORMER, ETownType::NECROPOLIS); - - case EMarketMode::RESOURCE_SKILL: - return hasBuilt(BuildingID::MAGIC_UNIVERSITY, ETownType::CONFLUX); - default: - assert(0); - return false; - } -} - -std::vector CGTownInstance::availableItemsIds(EMarketMode::EMarketMode mode) const -{ - if(mode == EMarketMode::RESOURCE_ARTIFACT) - { - std::vector ret; - for(const CArtifact *a : merchantArtifacts) - if(a) - ret.push_back(a->id); - else - ret.push_back(-1); - return ret; - } - else if ( mode == EMarketMode::RESOURCE_SKILL ) - { - return universitySkills; - } - else - return IMarket::availableItemsIds(mode); -} - -void CGTownInstance::updateAppearance() -{ - if (!hasFort()) - appearance.animationFile = town->clientInfo.advMapVillage; - else if(hasCapitol()) - appearance.animationFile = town->clientInfo.advMapCapitol; - else - appearance.animationFile = town->clientInfo.advMapCastle; -} - -std::string CGTownInstance::nodeName() const -{ - return "Town (" + (town ? town->faction->name : "unknown") + ") of " + name; -} - -void CGTownInstance::deserializationFix() -{ - attachTo(&townAndVis); - - //Hero is already handled by CGameState::attachArmedObjects - -// if(visitingHero) -// visitingHero->attachTo(&townAndVis); -// if(garrisonHero) -// garrisonHero->attachTo(this); -} - -void CGTownInstance::updateMoraleBonusFromArmy() -{ - Bonus *b = getBonusList().getFirst(Selector::sourceType(Bonus::ARMY).And(Selector::type(Bonus::MORALE))); - if(!b) - { - b = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1); - addNewBonus(b); - } - - if (garrisonHero) - b->val = 0; - else - CArmedInstance::updateMoraleBonusFromArmy(); -} - -void CGTownInstance::recreateBuildingsBonuses() -{ - static TPropagatorPtr playerProp(new CPropagatorNodeType(PLAYER)); - - BonusList bl; - getExportedBonusList().getBonuses(bl, Selector::sourceType(Bonus::TOWN_STRUCTURE)); - for(Bonus *b : bl) - removeBonus(b); - - //tricky! -> checks tavern only if no bratherhood of sword or not a castle - if(subID != ETownType::CASTLE || !addBonusIfBuilt(BuildingID::BROTHERHOOD, Bonus::MORALE, +2)) - addBonusIfBuilt(BuildingID::TAVERN, Bonus::MORALE, +1); - - if(subID == ETownType::CASTLE) //castle - { - addBonusIfBuilt(BuildingID::LIGHTHOUSE, Bonus::SEA_MOVEMENT, +500, playerProp); - addBonusIfBuilt(BuildingID::GRAIL, Bonus::MORALE, +2, playerProp); //colossus - } - else if(subID == ETownType::RAMPART) //rampart - { - addBonusIfBuilt(BuildingID::FOUNTAIN_OF_FORTUNE, Bonus::LUCK, +2); //fountain of fortune - addBonusIfBuilt(BuildingID::GRAIL, Bonus::LUCK, +2, playerProp); //guardian spirit - } - else if(subID == ETownType::TOWER) //tower - { - addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +15, PrimarySkill::KNOWLEDGE); //grail - } - else if(subID == ETownType::INFERNO) //Inferno - { - addBonusIfBuilt(BuildingID::STORMCLOUDS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER); //Brimstone Clouds - } - else if(subID == ETownType::NECROPOLIS) //necropolis - { - addBonusIfBuilt(BuildingID::COVER_OF_DARKNESS, Bonus::DARKNESS, +20); - addBonusIfBuilt(BuildingID::NECROMANCY_AMPLIFIER, Bonus::SECONDARY_SKILL_PREMY, +10, playerProp, SecondarySkill::NECROMANCY); //necromancy amplifier - addBonusIfBuilt(BuildingID::GRAIL, Bonus::SECONDARY_SKILL_PREMY, +20, playerProp, SecondarySkill::NECROMANCY); //Soul prison - } - else if(subID == ETownType::DUNGEON) //Dungeon - { - addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +12, PrimarySkill::SPELL_POWER); //grail - } - else if(subID == ETownType::STRONGHOLD) //Stronghold - { - addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +20, PrimarySkill::ATTACK); //grail - } - else if(subID == ETownType::FORTRESS) //Fortress - { - addBonusIfBuilt(BuildingID::GLYPHS_OF_FEAR, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE); //Glyphs of Fear - addBonusIfBuilt(BuildingID::BLOOD_OBELISK, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK); //Blood Obelisk - addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::ATTACK); //grail - addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::DEFENSE); //grail - } - else if(subID == ETownType::CONFLUX) - { - - } -} - -bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype /*= -1*/) -{ - static auto emptyPropagator = TPropagatorPtr(); - return addBonusIfBuilt(building, type, val, emptyPropagator, subtype); -} - -bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype /*= -1*/) -{ - if(hasBuilt(building)) - { - std::ostringstream descr; - descr << town->buildings.at(building)->Name() << " "; - if(val > 0) - descr << "+"; - else if(val < 0) - descr << "-"; - descr << val; - - Bonus *b = new Bonus(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, descr.str(), subtype); - if(prop) - b->addPropagator(prop); - addNewBonus(b); - return true; - } - - return false; -} - -void CGTownInstance::setVisitingHero(CGHeroInstance *h) -{ - assert(!!visitingHero == !h); - if(h) - { - PlayerState *p = cb->gameState()->getPlayer(h->tempOwner); - assert(p); - h->detachFrom(p); - h->attachTo(&townAndVis); - visitingHero = h; - h->visitedTown = this; - h->inTownGarrison = false; - } - else - { - PlayerState *p = cb->gameState()->getPlayer(visitingHero->tempOwner); - visitingHero->visitedTown = nullptr; - visitingHero->detachFrom(&townAndVis); - visitingHero->attachTo(p); - visitingHero = nullptr; - } -} - -void CGTownInstance::setGarrisonedHero(CGHeroInstance *h) -{ - assert(!!garrisonHero == !h); - if(h) - { - PlayerState *p = cb->gameState()->getPlayer(h->tempOwner); - assert(p); - h->detachFrom(p); - h->attachTo(this); - garrisonHero = h; - h->visitedTown = this; - h->inTownGarrison = true; - } - else - { - PlayerState *p = cb->gameState()->getPlayer(garrisonHero->tempOwner); - garrisonHero->visitedTown = nullptr; - garrisonHero->inTownGarrison = false; - garrisonHero->detachFrom(this); - garrisonHero->attachTo(p); - garrisonHero = nullptr; - } - updateMoraleBonusFromArmy(); //avoid giving morale bonus for same army twice -} - -bool CGTownInstance::armedGarrison() const -{ - return stacksCount() || garrisonHero; -} - -int CGTownInstance::getTownLevel() const -{ - // count all buildings that are not upgrades - return boost::range::count_if(builtBuildings, [&](const BuildingID & build) - { - return town->buildings.at(build) && town->buildings.at(build)->upgrade == -1; - }); -} - -CBonusSystemNode * CGTownInstance::whatShouldBeAttached() -{ - return &townAndVis; -} - -const CArmedInstance * CGTownInstance::getUpperArmy() const -{ - if(garrisonHero) - return garrisonHero; - return this; -} - -bool CGTownInstance::hasBuilt(BuildingID buildingID, int townID) const -{ - if (townID == town->faction->index || townID == ETownType::ANY) - return hasBuilt(buildingID); - return false; -} - -bool CGTownInstance::hasBuilt(BuildingID buildingID) const -{ - return vstd::contains(builtBuildings, buildingID); -} - -void CGTownInstance::addHeroToStructureVisitors( const CGHeroInstance *h, si32 structureInstanceID ) const -{ - if(visitingHero == h) - cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_VISITING_HERO, structureInstanceID); //add to visitors - else if(garrisonHero == h) - cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_GARRISONED_HERO, structureInstanceID); //then it must be garrisoned hero - else - { - //should never ever happen - logGlobal->errorStream() << "Cannot add hero " << h->name << " to visitors of structure #" << structureInstanceID; - assert(0); - } -} - -void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if(result.winner == 0) - { - removeCapitols(hero->getOwner()); - cb->setOwner (this, hero->tempOwner); //give control after checkout is done - FoWChange fw; - fw.player = hero->tempOwner; - fw.mode = 1; - getSightTiles (fw.tiles); //update visibility for castle structures - cb->sendAndApply (&fw); - } -} - -bool CGVisitableOPH::wasVisited (const CGHeroInstance * h) const -{ - return vstd::contains(visitors, h->id); -} - -void CGVisitableOPH::onHeroVisit( const CGHeroInstance * h ) const -{ - if(!vstd::contains(visitors, h->id)) - { - onNAHeroVisit (h, false); - switch(ID) - { - case Obj::TREE_OF_KNOWLEDGE: - case Obj::ARENA: - case Obj::LIBRARY_OF_ENLIGHTENMENT: - case Obj::SCHOOL_OF_MAGIC: - case Obj::SCHOOL_OF_WAR: - break; - default: - cb->setObjProperty(id, ObjProperty::VISITORS, h->id.getNum()); //add to the visitors - break; - } - } - else - { - onNAHeroVisit(h, true); - } -} - -void CGVisitableOPH::initObj() -{ - if(ID==Obj::TREE_OF_KNOWLEDGE) - { - switch (cb->gameState()->getRandomGenerator().nextInt(2)) - { - case 1: - treePrice[Res::GOLD] = 2000; - break; - case 2: - treePrice[Res::GEMS] = 10; - break; - default: - break; - } - } -} - -void CGVisitableOPH::treeSelected (const CGHeroInstance * h, ui32 result) const -{ - if(result) //player agreed to give res for exp - { - si64 expToGive = VLC->heroh->reqExp(h->level+1) - VLC->heroh->reqExp(h->level);; - cb->giveResources (h->getOwner(), -treePrice); - cb->changePrimSkill (h, PrimarySkill::EXPERIENCE, expToGive); - cb->setObjProperty (id, ObjProperty::VISITORS, h->id.getNum()); //add to the visitors - } -} -void CGVisitableOPH::onNAHeroVisit (const CGHeroInstance * h, bool alreadyVisited) const -{ - Component::EComponentType c_id = Component::PRIM_SKILL; //most used here - int subid=0, ot=0, sound = 0; - TExpType val=1; - switch(ID) - { - case Obj::ARENA: - sound = soundBase::NOMAD; - ot = 0; - break; - case Obj::MERCENARY_CAMP: - sound = soundBase::NOMAD; - subid=PrimarySkill::ATTACK; - ot=80; - break; - case Obj::MARLETTO_TOWER: - sound = soundBase::NOMAD; - subid=PrimarySkill::DEFENSE; - ot=39; - break; - case Obj::STAR_AXIS: - sound = soundBase::gazebo; - subid=PrimarySkill::SPELL_POWER; - ot=100; - break; - case Obj::GARDEN_OF_REVELATION: - sound = soundBase::GETPROTECTION; - subid=PrimarySkill::KNOWLEDGE; - ot=59; - break; - case Obj::LEARNING_STONE: - sound = soundBase::gazebo; - c_id=Component::EXPERIENCE; - ot=143; - val=1000; - break; - case Obj::TREE_OF_KNOWLEDGE: - sound = soundBase::gazebo; - c_id = Component::EXPERIENCE; - subid = 1; - ot = 147; - val = 1; - break; - case Obj::LIBRARY_OF_ENLIGHTENMENT: - sound = soundBase::gazebo; - ot = 66; - break; - case Obj::SCHOOL_OF_MAGIC: - sound = soundBase::faerie; - ot = 71; - break; - case Obj::SCHOOL_OF_WAR: - c_id=Component::PRIM_SKILL; - sound = soundBase::MILITARY; - ot = 158; - break; - } - if (!alreadyVisited) - { - switch (ID) - { - case Obj::ARENA: - { - BlockingDialog sd(false,true); - sd.soundID = sound; - sd.text.addTxt(MetaString::ADVOB_TXT,ot); - sd.components.push_back(Component(c_id, PrimarySkill::ATTACK, 2, 0)); - sd.components.push_back(Component(c_id, PrimarySkill::DEFENSE, 2, 0)); - sd.player = h->getOwner(); - cb->showBlockingDialog(&sd); - return; - } - case Obj::MERCENARY_CAMP: - case Obj::MARLETTO_TOWER: - case Obj::STAR_AXIS: - case Obj::GARDEN_OF_REVELATION: - { - cb->changePrimSkill (h, static_cast(subid), val); - InfoWindow iw; - iw.soundID = sound; - iw.components.push_back(Component(c_id, subid, val, 0)); - iw.text.addTxt(MetaString::ADVOB_TXT,ot); - iw.player = h->getOwner(); - cb->showInfoDialog(&iw); - break; - } - case Obj::LEARNING_STONE: //give exp - { - val = h->calculateXp(val); - InfoWindow iw; - iw.soundID = sound; - iw.components.push_back (Component(c_id,subid,val,0)); - iw.player = h->getOwner(); - iw.text.addTxt(MetaString::ADVOB_TXT,ot); - cb->showInfoDialog(&iw); - cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, val); - break; - } - case Obj::TREE_OF_KNOWLEDGE: - { - val = VLC->heroh->reqExp (h->level + val) - VLC->heroh->reqExp(h->level); - if(!treePrice.nonZero()) - { - cb->setObjProperty (id, ObjProperty::VISITORS, h->id.getNum()); //add to the visitors - InfoWindow iw; - iw.soundID = sound; - iw.components.push_back (Component(c_id,subid,1,0)); - iw.player = h->getOwner(); - iw.text.addTxt (MetaString::ADVOB_TXT,148); - cb->showInfoDialog (&iw); - cb->changePrimSkill (h, PrimarySkill::EXPERIENCE, val); - break; - } - else - { - if(treePrice[Res::GOLD] > 0) - ot = 149; - else - ot = 151; - - if(!cb->getPlayer(h->tempOwner)->resources.canAfford(treePrice)) //not enough resources - { - ot++; - showInfoDialog(h,ot,sound); - return; - } - - BlockingDialog sd (true, false); - sd.soundID = sound; - sd.player = h->getOwner(); - sd.text.addTxt (MetaString::ADVOB_TXT,ot); - sd.addResourceComponents (treePrice); - cb->showBlockingDialog (&sd); - } - break; - } - case Obj::LIBRARY_OF_ENLIGHTENMENT: - { - int txt_id = 66; - if(h->level < 10 - 2*h->getSecSkillLevel(SecondarySkill::DIPLOMACY)) //not enough level - { - txt_id += 2; - } - else - { - cb->setObjProperty(id, ObjProperty::VISITORS, h->id.getNum()); //add to the visitors - cb->changePrimSkill (h, PrimarySkill::ATTACK, 2); - cb->changePrimSkill (h, PrimarySkill::DEFENSE, 2); - cb->changePrimSkill (h, PrimarySkill::KNOWLEDGE, 2); - cb->changePrimSkill (h, PrimarySkill::SPELL_POWER, 2); - } - showInfoDialog(h,txt_id,sound); - break; - } - case Obj::SCHOOL_OF_MAGIC: - case Obj::SCHOOL_OF_WAR: - { - int skill = (ID==Obj::SCHOOL_OF_MAGIC ? 2 : 0); - if (cb->getResource (h->getOwner(), Res::GOLD) < 1000) //not enough resources - { - showInfoDialog (h->getOwner(), ot+2, sound); - } - else - { - BlockingDialog sd(true,true); - sd.soundID = sound; - sd.player = h->getOwner(); - sd.text.addTxt(MetaString::ADVOB_TXT,ot); - sd.components.push_back(Component(c_id, skill, +1, 0)); - sd.components.push_back(Component(c_id, skill+1, +1, 0)); - cb->showBlockingDialog(&sd); - } - } - break; - } - } - else - { - ot++; - showInfoDialog (h->getOwner(),ot,sound); - } -} - -const std::string & CGVisitableOPH::getHoverText() const -{ - int pom = -1; - switch(ID) - { - case Obj::ARENA: - pom = -1; - break; - case Obj::MERCENARY_CAMP: - pom = 8; - break; - case Obj::MARLETTO_TOWER: - pom = 7; - break; - case Obj::STAR_AXIS: - pom = 11; - break; - case Obj::GARDEN_OF_REVELATION: - pom = 4; - break; - case Obj::LEARNING_STONE: - pom = 5; - break; - case Obj::TREE_OF_KNOWLEDGE: - pom = 18; - break; - case Obj::LIBRARY_OF_ENLIGHTENMENT: - break; - case Obj::SCHOOL_OF_MAGIC: - pom = 9; - break; - case Obj::SCHOOL_OF_WAR: - pom = 10; - break; - default: - throw std::runtime_error("Wrong CGVisitableOPH object ID!\n"); - } - hoverName = VLC->generaltexth->names[ID]; - if(pom >= 0) - hoverName += ("\n" + VLC->generaltexth->xtrainfo[pom]); - const CGHeroInstance *h = cb->getSelectedHero (cb->getCurrentPlayer()); - if(h) - { - hoverName += "\n\n"; - bool visited = vstd::contains (visitors, h->id); - hoverName += visitedTxt (visited); - } - return hoverName; -} - -void CGVisitableOPH::arenaSelected(const CGHeroInstance * h, int primSkill ) const -{ - cb->setObjProperty(id, ObjProperty::VISITORS, h->id.getNum()); //add to the visitors - cb->changePrimSkill(h, static_cast(primSkill-1), 2); -} - -void CGVisitableOPH::setPropertyDer( ui8 what, ui32 val ) -{ - if(what == ObjProperty::VISITORS) - visitors.insert(ObjectInstanceID(val)); -} - -void CGVisitableOPH::schoolSelected(const CGHeroInstance * h, ui32 which) const -{ - if(!which) //player refused to pay - return; - - int base = (ID == Obj::SCHOOL_OF_MAGIC ? 2 : 0); - cb->setObjProperty (id, ObjProperty::VISITORS, h->id.getNum()); //add to the visitors - cb->giveResource (h->getOwner(),Res::GOLD,-1000); //take 1000 gold - cb->changePrimSkill (h, static_cast(base + which-1), +1); //give appropriate skill -} - -void CGVisitableOPH::blockingDialogAnswered(const CGHeroInstance *h, ui32 answer) const -{ - switch (ID) - { - case Obj::ARENA: - arenaSelected(h, answer); - break; - - case Obj::TREE_OF_KNOWLEDGE: - treeSelected(h, answer); - break; - - case Obj::SCHOOL_OF_MAGIC: - case Obj::SCHOOL_OF_WAR: - schoolSelected(h, answer); - break; - - default: - assert(0); - break; - } -} - -COPWBonus::COPWBonus (BuildingID index, CGTownInstance *TOWN) -{ - ID = index; - town = TOWN; - id = town->bonusingBuildings.size(); -} -void COPWBonus::setProperty(ui8 what, ui32 val) -{ - switch (what) - { - case ObjProperty::VISITORS: - visitors.insert(val); - break; - case ObjProperty::STRUCTURE_CLEAR_VISITORS: - visitors.clear(); - break; - } -} -void COPWBonus::onHeroVisit (const CGHeroInstance * h) const -{ - ObjectInstanceID heroID = h->id; - if (town->hasBuilt(ID)) - { - InfoWindow iw; - iw.player = h->tempOwner; - switch (town->subID) - { - case ETownType::CASTLE: //Stables - if (!h->hasBonusFrom(Bonus::OBJECT, Obj::STABLES)) //does not stack with advMap Stables - { - GiveBonus gb; - gb.bonus = Bonus(Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, Bonus::OBJECT, 600, 94, VLC->generaltexth->arraytxt[100]); - gb.id = heroID.getNum(); - cb->giveHeroBonus(&gb); - iw.text << VLC->generaltexth->allTexts[580]; - cb->showInfoDialog(&iw); - } - break; - case ETownType::DUNGEON: //Mana Vortex - if (visitors.empty() && h->mana <= h->manaLimit() * 2) - { - cb->setManaPoints (heroID, 2 * h->manaLimit()); - //TODO: investigate line below - //cb->setObjProperty (town->id, ObjProperty::VISITED, true); - iw.text << VLC->generaltexth->allTexts[579]; - cb->showInfoDialog(&iw); - town->addHeroToStructureVisitors(h, id); - } - break; - } - } -} -CTownBonus::CTownBonus (BuildingID index, CGTownInstance *TOWN) -{ - ID = index; - town = TOWN; - id = town->bonusingBuildings.size(); -} -void CTownBonus::setProperty (ui8 what, ui32 val) -{ - if(what == ObjProperty::VISITORS) - visitors.insert(ObjectInstanceID(val)); -} -void CTownBonus::onHeroVisit (const CGHeroInstance * h) const -{ - ObjectInstanceID heroID = h->id; - if (town->hasBuilt(ID) && visitors.find(heroID) == visitors.end()) - { - InfoWindow iw; - PrimarySkill::PrimarySkill what = PrimarySkill::ATTACK; - int val=0, mid=0; - switch (ID) - { - case BuildingID::SPECIAL_4: - switch(town->subID) - { - case ETownType::TOWER: //wall - what = PrimarySkill::KNOWLEDGE; - val = 1; - mid = 581; - iw.components.push_back (Component(Component::PRIM_SKILL, 3, 1, 0)); - break; - case ETownType::INFERNO: //order of fire - what = PrimarySkill::SPELL_POWER; - val = 1; - mid = 582; - iw.components.push_back (Component(Component::PRIM_SKILL, 2, 1, 0)); - break; - case ETownType::STRONGHOLD://hall of Valhalla - what = PrimarySkill::ATTACK; - val = 1; - mid = 584; - iw.components.push_back (Component(Component::PRIM_SKILL, 0, 1, 0)); - break; - case ETownType::DUNGEON://academy of battle scholars - what = PrimarySkill::EXPERIENCE; - val = h->calculateXp(1000); - mid = 583; - iw.components.push_back (Component(Component::EXPERIENCE, 0, val, 0)); - break; - } - break; - case BuildingID::SPECIAL_1: - switch(town->subID) - { - case ETownType::FORTRESS: //cage of warlords - what = PrimarySkill::DEFENSE; - val = 1; - mid = 585; - iw.components.push_back (Component(Component::PRIM_SKILL, 1, 1, 0)); - break; - } - break; - } - assert(mid); - iw.player = cb->getOwner(heroID); - iw.text << VLC->generaltexth->allTexts[mid]; - cb->showInfoDialog(&iw); - cb->changePrimSkill (cb->getHero(heroID), what, val); - town->addHeroToStructureVisitors(h, id); - } -} -const std::string & CGCreature::getHoverText() const -{ - if(stacks.empty()) - { - static const std::string errorValue("!!!INVALID_STACK!!!"); - - //should not happen... - logGlobal->errorStream() << "Invalid stack at tile " << pos << ": subID=" << subID << "; id=" << id; - return errorValue; // references to temporary are illegal - use pre-constructed string - } - - MetaString ms; - int pom = stacks.begin()->second->getQuantityID(); - pom = 172 + 3*pom; - ms.addTxt(MetaString::ARRAY_TXT,pom); - ms << " " ; - ms.addTxt(MetaString::CRE_PL_NAMES,subID); - ms.toString(hoverName); - - if(const CGHeroInstance *selHero = cb->getSelectedHero(cb->getCurrentPlayer())) - { - const JsonNode & texts = VLC->generaltexth->localizedTexts["adventureMap"]["monsterThreat"]; - - hoverName += texts["title"].String(); - int choice; - double ratio = ((double)getArmyStrength() / selHero->getTotalStrength()); - if (ratio < 0.1) choice = 0; - else if (ratio < 0.25) choice = 1; - else if (ratio < 0.6) choice = 2; - else if (ratio < 0.9) choice = 3; - else if (ratio < 1.1) choice = 4; - else if (ratio < 1.3) choice = 5; - else if (ratio < 1.8) choice = 6; - else if (ratio < 2.5) choice = 7; - else if (ratio < 4) choice = 8; - else if (ratio < 8) choice = 9; - else if (ratio < 20) choice = 10; - else choice = 11; - hoverName += texts["levels"].Vector()[choice].String(); - } - return hoverName; -} -void CGCreature::onHeroVisit( const CGHeroInstance * h ) const -{ - int action = takenAction(h); - switch( action ) //decide what we do... - { - case FIGHT: - fight(h); - break; - case FLEE: //flee - { - flee(h); - break; - } - case JOIN_FOR_FREE: //join for free - { - BlockingDialog ynd(true,false); - ynd.player = h->tempOwner; - ynd.text.addTxt(MetaString::ADVOB_TXT, 86); - ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID); - cb->showBlockingDialog(&ynd); - break; - } - default: //join for gold - { - assert(action > 0); - - //ask if player agrees to pay gold - BlockingDialog ynd(true,false); - ynd.player = h->tempOwner; - std::string tmp = VLC->generaltexth->advobtxt[90]; - boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast(getStackCount(SlotID(0)))); - boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast(action)); - boost::algorithm::replace_first(tmp,"%s",VLC->creh->creatures[subID]->namePl); - ynd.text << tmp; - cb->showBlockingDialog(&ynd); - break; - } - } - -} - -void CGCreature::initObj() -{ - blockVisit = true; - switch(character) - { - case 0: - character = -4; - break; - case 1: - character = cb->gameState()->getRandomGenerator().nextInt(1, 7); - break; - case 2: - character = cb->gameState()->getRandomGenerator().nextInt(1, 10); - break; - case 3: - character = cb->gameState()->getRandomGenerator().nextInt(4, 10); - break; - case 4: - character = 10; - break; - } - - stacks[SlotID(0)]->setType(CreatureID(subID)); - TQuantity &amount = stacks[SlotID(0)]->count; - CCreature &c = *VLC->creh->creatures[subID]; - if(amount == 0) - { - amount = cb->gameState()->getRandomGenerator().nextInt(c.ammMin, c.ammMax); - - if(amount == 0) //armies with 0 creatures are illegal - { - logGlobal->warnStream() << "Problem: stack " << nodeName() << " cannot have 0 creatures. Check properties of " << c.nodeName(); - amount = 1; - } - } - formation.randomFormation = cb->gameState()->getRandomGenerator().nextInt(); - - temppower = stacks[SlotID(0)]->count * 1000; - refusedJoining = false; -} - -void CGCreature::newTurn() const -{//Works only for stacks of single type of size up to 2 millions - if (stacks.begin()->second->count < VLC->modh->settings.CREEP_SIZE && cb->getDate(Date::DAY_OF_WEEK) == 1 && cb->getDate(Date::DAY) > 1) - { - ui32 power = temppower * (100 + VLC->modh->settings.WEEKLY_GROWTH)/100; - cb->setObjProperty(id, ObjProperty::MONSTER_COUNT, std::min (power/1000 , (ui32)VLC->modh->settings.CREEP_SIZE)); //set new amount - cb->setObjProperty(id, ObjProperty::MONSTER_POWER, power); //increase temppower - } - if (VLC->modh->modules.STACK_EXP) - cb->setObjProperty(id, ObjProperty::MONSTER_EXP, VLC->modh->settings.NEUTRAL_STACK_EXP); //for testing purpose -} -void CGCreature::setPropertyDer(ui8 what, ui32 val) -{ - switch (what) - { - case ObjProperty::MONSTER_COUNT: - stacks[SlotID(0)]->count = val; - break; - case ObjProperty::MONSTER_POWER: - temppower = val; - break; - case ObjProperty::MONSTER_EXP: - giveStackExp(val); - break; - case ObjProperty::MONSTER_RESTORE_TYPE: - formation.basicType = val; - break; - case ObjProperty::MONSTER_REFUSED_JOIN: - refusedJoining = val; - break; - } -} - -int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const -{ - //calculate relative strength of hero and creatures armies - double relStrength = double(h->getTotalStrength()) / getArmyStrength(); - - int powerFactor; - if(relStrength >= 7) - powerFactor = 11; - - else if(relStrength >= 1) - powerFactor = (int)(2*(relStrength-1)); - - else if(relStrength >= 0.5) - powerFactor = -1; - - else if(relStrength >= 0.333) - powerFactor = -2; - else - powerFactor = -3; - - std::set myKindCres; //what creatures are the same kind as we - const CCreature * myCreature = VLC->creh->creatures[subID]; - myKindCres.insert(myCreature->idNumber); //we - myKindCres.insert(myCreature->upgrades.begin(), myCreature->upgrades.end()); //our upgrades - - for(ConstTransitivePtr &crea : VLC->creh->creatures) - { - if(vstd::contains(crea->upgrades, myCreature->idNumber)) //it's our base creatures - myKindCres.insert(crea->idNumber); - } - - int count = 0, //how many creatures of similar kind has hero - totalCount = 0; - - for (auto & elem : h->Slots()) - { - if(vstd::contains(myKindCres,elem.second->type->idNumber)) - count += elem.second->count; - totalCount += elem.second->count; - } - - int sympathy = 0; // 0 if hero have no similar creatures - if(count) - sympathy++; // 1 - if hero have at least 1 similar creature - if(count*2 > totalCount) - sympathy++; // 2 - hero have similar creatures more that 50% - - int charisma = powerFactor + h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy; - - if(charisma < character) //creatures will fight - return -2; - - if (allowJoin) - { - if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy + 1 >= character) - return 0; //join for free - - else if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) * 2 + sympathy + 1 >= character) - return VLC->creh->creatures[subID]->cost[6] * getStackCount(SlotID(0)); //join for gold - } - - //we are still here - creatures have not joined hero, flee or fight - - if (charisma > character) - return -1; //flee - else - return -2; //fight -} - -void CGCreature::fleeDecision(const CGHeroInstance *h, ui32 pursue) const -{ - if(refusedJoining) - cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, false); - - if(pursue) - { - fight(h); - } - else - { - cb->removeObject(this); - } -} - -void CGCreature::joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const -{ - if(!accept) - { - if(takenAction(h,false) == -1) //they flee - { - cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, true); - flee(h); - } - else //they fight - { - showInfoDialog(h,87,0);//Insulted by your refusal of their offer, the monsters attack! - fight(h); - } - } - else //accepted - { - if (cb->getResource(h->tempOwner, Res::GOLD) < cost) //player don't have enough gold! - { - InfoWindow iw; - iw.player = h->tempOwner; - iw.text << std::pair(1,29); //You don't have enough gold - cb->showInfoDialog(&iw); - - //act as if player refused - joinDecision(h,cost,false); - return; - } - - //take gold - if(cost) - cb->giveResource(h->tempOwner,Res::GOLD,-cost); - - cb->tryJoiningArmy(this, h, true, true); - } -} - -void CGCreature::fight( const CGHeroInstance *h ) const -{ - //split stacks - //TODO: multiple creature types in a stack? - int basicType = stacks.begin()->second->type->idNumber; - cb->setObjProperty(id, ObjProperty::MONSTER_RESTORE_TYPE, basicType); //store info about creature stack - - double relativePower = static_cast(h->getTotalStrength()) / getArmyStrength(); - int stacksCount; - //TODO: number depends on tile type - if (relativePower < 0.5) - { - stacksCount = 7; - } - else if (relativePower < 0.67) - { - stacksCount = 7; - } - else if (relativePower < 1) - { - stacksCount = 6; - } - else if (relativePower < 1.5) - { - stacksCount = 5; - } - else if (relativePower < 2) - { - stacksCount = 4; - } - else - { - stacksCount = 3; - } - SlotID sourceSlot = stacks.begin()->first; - SlotID destSlot; - for (int stacksLeft = stacksCount; stacksLeft > 1; --stacksLeft) - { - int stackSize = stacks.begin()->second->count / stacksLeft; - if (stackSize) - { - if ((destSlot = getFreeSlot()).validSlot()) - cb->moveStack(StackLocation(this, sourceSlot), StackLocation(this, destSlot), stackSize); - else - { - logGlobal->warnStream() <<"Warning! Not enough empty slots to split stack!"; - break; - } - } - else break; - } - if (stacksCount > 1) - { - if (formation.randomFormation % 100 < 50) //upgrade - { - SlotID slotId = SlotID(stacks.size() / 2); - const auto & upgrades = getStack(slotId).type->upgrades; - if(!upgrades.empty()) - { - auto it = RandomGeneratorUtil::nextItem(upgrades, cb->gameState()->getRandomGenerator()); - cb->changeStackType(StackLocation(this, slotId), VLC->creh->creatures[*it]); - } - } - } - - cb->startBattleI(h, this); - -} - -void CGCreature::flee( const CGHeroInstance * h ) const -{ - BlockingDialog ynd(true,false); - ynd.player = h->tempOwner; - ynd.text.addTxt(MetaString::ADVOB_TXT,91); - ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID); - cb->showBlockingDialog(&ynd); -} - -void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - - if(result.winner==0) - { - cb->removeObject(this); - } - else - { - //int killedAmount=0; - //for(std::set >::iterator i=result->casualties[1].begin(); i!=result->casualties[1].end(); i++) - // if(i->first == subID) - // killedAmount += i->second; - //cb->setAmount(id, slots.find(0)->second.second - killedAmount); - - /* - MetaString ms; - int pom = slots.find(0)->second.getQuantityID(); - pom = 174 + 3*pom + 1; - ms << std::pair(6,pom) << " " << std::pair(7,subID); - cb->setHoverName(id,&ms); - cb->setObjProperty(id, 11, slots.begin()->second.count * 1000); - */ - - //merge stacks into one - TSlots::const_iterator i; - CCreature * cre = VLC->creh->creatures[formation.basicType]; - for (i = stacks.begin(); i != stacks.end(); i++) - { - if (cre->isMyUpgrade(i->second->type)) - { - cb->changeStackType (StackLocation(this, i->first), cre); //un-upgrade creatures - } - } - - //first stack has to be at slot 0 -> if original one got killed, move there first remaining stack - if(!hasStackAtSlot(SlotID(0))) - cb->moveStack(StackLocation(this, stacks.begin()->first), StackLocation(this, SlotID(0)), stacks.begin()->second->count); - - while (stacks.size() > 1) //hopefully that's enough - { - // TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere) - i = stacks.end(); - i--; - SlotID slot = getSlotFor(i->second->type); - if (slot == i->first) //no reason to move stack to its own slot - break; - else - cb->moveStack (StackLocation(this, i->first), StackLocation(this, slot), i->second->count); - } - - cb->setObjProperty(id, ObjProperty::MONSTER_POWER, stacks.begin()->second->count * 1000); //remember casualties - } -} - -void CGCreature::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - auto action = takenAction(hero); - if(!refusedJoining && action >= JOIN_FOR_FREE) //higher means price - joinDecision(hero, action, answer); - else if(action != FIGHT) - fleeDecision(hero, answer); - else - assert(0); -} - -void CGMine::onHeroVisit( const CGHeroInstance * h ) const -{ - int relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); - - if(relations == 2) //we're visiting our mine - { - cb->showGarrisonDialog(id,h->id,true); - return; - } - else if (relations == 1)//ally - return; - - if(stacksCount()) //Mine is guarded - { - BlockingDialog ynd(true,false); - ynd.player = h->tempOwner; - ynd.text.addTxt(MetaString::ADVOB_TXT, subID == 7 ? 84 : 187); - cb->showBlockingDialog(&ynd); - return; - } - - flagMine(h->tempOwner); - -} - -void CGMine::newTurn() const -{ - if(cb->getDate() == 1) - return; - - if (tempOwner == PlayerColor::NEUTRAL) - return; - - cb->giveResource(tempOwner, producedResource, producedQuantity); -} - -void CGMine::initObj() -{ - if(subID >= 7) //Abandoned Mine - { - //set guardians - int howManyTroglodytes = cb->gameState()->getRandomGenerator().nextInt(100, 199); - auto troglodytes = new CStackInstance(CreatureID::TROGLODYTES, howManyTroglodytes); - putStack(SlotID(0), troglodytes); - - //after map reading tempOwner placeholds bitmask for allowed resources - std::vector possibleResources; - for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) - if(tempOwner.getNum() & 1<(i)); - - assert(!possibleResources.empty()); - producedResource = *RandomGeneratorUtil::nextItem(possibleResources, cb->gameState()->getRandomGenerator()); - tempOwner = PlayerColor::NEUTRAL; - hoverName = VLC->generaltexth->mines[7].first + "\n" + VLC->generaltexth->allTexts[202] + " " + troglodytes->getQuantityTXT(false) + " " + troglodytes->type->namePl; - } - else - { - producedResource = static_cast(subID); - - MetaString ms; - ms << std::pair(9,producedResource); - if(tempOwner >= PlayerColor::PLAYER_LIMIT) - tempOwner = PlayerColor::NEUTRAL; - else - ms << " (" << std::pair(6,23+tempOwner.getNum()) << ")"; - ms.toString(hoverName); - } - - producedQuantity = defaultResProduction(); -} - -void CGMine::flagMine(PlayerColor player) const -{ - assert(tempOwner != player); - cb->setOwner(this, player); //not ours? flag it! - - MetaString ms; - ms << std::pair(9,subID) << "\n(" << std::pair(6,23+player.getNum()) << ")"; - if(subID == 7) - { - ms << "(%s)"; - ms.addReplacement(MetaString::RES_NAMES, producedResource); - } - cb->setHoverName(this,&ms); - - InfoWindow iw; - iw.soundID = soundBase::FLAGMINE; - iw.text.addTxt(MetaString::MINE_EVNTS,producedResource); //not use subID, abandoned mines uses default mine texts - iw.player = player; - iw.components.push_back(Component(Component::RESOURCE,producedResource,producedQuantity,-1)); - cb->showInfoDialog(&iw); -} - -ui32 CGMine::defaultResProduction() -{ - switch(producedResource) - { - case Res::WOOD: - case Res::ORE: - return 2; - case Res::GOLD: - return 1000; - default: - return 1; - } -} - -void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if(result.winner == 0) //attacker won - { - if(subID == 7) - { - showInfoDialog(hero->tempOwner, 85, 0); - } - flagMine(hero->tempOwner); - } -} - -void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if(answer) - cb->startBattleI(hero, this); -} - -void CGResource::initObj() -{ - blockVisit = true; - hoverName = VLC->generaltexth->restypes[subID]; - - if(!amount) - { - switch(subID) - { - case 6: - amount = cb->gameState()->getRandomGenerator().nextInt(500, 1000); - break; - case 0: case 2: - amount = cb->gameState()->getRandomGenerator().nextInt(6, 10); - break; - default: - amount = cb->gameState()->getRandomGenerator().nextInt(3, 5); - break; - } - } -} - -void CGResource::onHeroVisit( const CGHeroInstance * h ) const -{ - if(stacksCount()) - { - if(message.size()) - { - BlockingDialog ynd(true,false); - ynd.player = h->getOwner(); - ynd.text << message; - cb->showBlockingDialog(&ynd); - } - else - { - blockingDialogAnswered(h, true); //behave as if player accepted battle - } - } - else - { - if(message.length()) - { - InfoWindow iw; - iw.player = h->tempOwner; - iw.text << message; - cb->showInfoDialog(&iw); - } - collectRes(h->getOwner()); - } -} - -void CGResource::collectRes( PlayerColor player ) const -{ - cb->giveResource(player, static_cast(subID), amount); - ShowInInfobox sii; - sii.player = player; - sii.c = Component(Component::RESOURCE,subID,amount,0); - sii.text.addTxt(MetaString::ADVOB_TXT,113); - sii.text.addReplacement(MetaString::RES_NAMES, subID); - cb->showCompInfo(&sii); - cb->removeObject(this); -} - -void CGResource::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if(result.winner == 0) //attacker won - collectRes(hero->getOwner()); -} - -void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if(answer) - cb->startBattleI(hero, this); -} - -void CGVisitableOPW::newTurn() const -{ - if (cb->getDate(Date::DAY_OF_WEEK) == 1) //first day of week = 1 - { - cb->setObjProperty(id, ObjProperty::VISITED, false); - MetaString ms; //set text to "not visited" - ms << std::pair(3,ID) << " " << std::pair(1,353); - cb->setHoverName(this,&ms); - } -} -bool CGVisitableOPW::wasVisited(PlayerColor player) const -{ - return visited; //TODO: other players should see object as unvisited -} - -void CGVisitableOPW::onHeroVisit( const CGHeroInstance * h ) const -{ - int mid=0, sound = 0; - switch (ID) - { - case Obj::MYSTICAL_GARDEN: - sound = soundBase::experience; - mid = 92; - break; - case Obj::WINDMILL: - sound = soundBase::GENIE; - mid = 170; - break; - case Obj::WATER_WHEEL: - sound = soundBase::GENIE; - mid = 164; - break; - default: - assert(0); - } - if (visited) - { - if (ID!=Obj::WINDMILL) - mid++; - else - mid--; - showInfoDialog(h,mid,sound); - } - else - { - Component::EComponentType type = Component::RESOURCE; - Res::ERes sub=Res::WOOD; - int val=0; - auto & rand = cb->gameState()->getRandomGenerator(); - - switch (ID) - { - case Obj::MYSTICAL_GARDEN: - if(rand.nextInt(1) == 1) - { - sub = Res::GEMS; - val = 5; - } - else - { - sub = Res::GOLD; - val = 500; - } - break; - case Obj::WINDMILL: - mid = 170; - sub = static_cast(rand.nextInt(1, 5)); - val = rand.nextInt(3, 6); - break; - case Obj::WATER_WHEEL: - mid = 164; - sub = Res::GOLD; - if(cb->getDate(Date::DAY)<8) - val = 500; - else - val = 1000; - } - cb->giveResource(h->tempOwner, sub, val); - InfoWindow iw; - iw.soundID = sound; - iw.player = h->tempOwner; - iw.components.push_back(Component(type,sub,val,0)); - iw.text.addTxt(MetaString::ADVOB_TXT,mid); - cb->showInfoDialog(&iw); - cb->setObjProperty(id, ObjProperty::VISITED, true); - MetaString ms; //set text to "visited" - ms.addTxt(MetaString::OBJ_NAMES,ID); ms << " "; ms.addTxt(MetaString::GENERAL_TXT,352); - cb->setHoverName(this,&ms); - } -} - -void CGVisitableOPW::setPropertyDer( ui8 what, ui32 val ) -{ - if(what == ObjProperty::VISITED) - visited = val; -} - -void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const -{ - ObjectInstanceID destinationid; - switch(ID) - { - case Obj::MONOLITH_ONE_WAY_ENTRANCE: //one way - find corresponding exit monolith - { - if(vstd::contains(objs,Obj::MONOLITH_ONE_WAY_EXIT) && vstd::contains(objs[Obj::MONOLITH_ONE_WAY_EXIT],subID) && objs[Obj::MONOLITH_ONE_WAY_EXIT][subID].size()) - { - destinationid = *RandomGeneratorUtil::nextItem(objs[Obj::MONOLITH_ONE_WAY_EXIT][subID], cb->gameState()->getRandomGenerator()); - } - else - { - logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; - } - break; - } - case Obj::MONOLITH_TWO_WAY://two way monolith - pick any other one - case Obj::WHIRLPOOL: //Whirlpool - if(vstd::contains(objs,ID) && vstd::contains(objs[ID],subID) && objs[ID][subID].size()>1) - { - //choose another exit - do - { - destinationid = *RandomGeneratorUtil::nextItem(objs[ID][subID], cb->gameState()->getRandomGenerator()); - } while(destinationid == id); - - if (ID == Obj::WHIRLPOOL) - { - if (!h->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION)) - { - if (h->Slots().size() > 1 || h->Slots().begin()->second->count > 1) - { //we can't remove last unit - SlotID targetstack = h->Slots().begin()->first; //slot numbers may vary - for(auto i = h->Slots().rbegin(); i != h->Slots().rend(); i++) - { - if (h->getPower(targetstack) > h->getPower(i->first)) - { - targetstack = (i->first); - } - } - - TQuantity countToTake = h->getStackCount(targetstack) * 0.5; - vstd::amax(countToTake, 1); - - - InfoWindow iw; - iw.player = h->tempOwner; - iw.text.addTxt (MetaString::ADVOB_TXT, 168); - iw.components.push_back (Component(CStackBasicDescriptor(h->getCreature(targetstack), countToTake))); - cb->showInfoDialog(&iw); - cb->changeStackCount(StackLocation(h, targetstack), -countToTake); - } - } - } - } - else - logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; - break; - case Obj::SUBTERRANEAN_GATE: //find nearest subterranean gate on the other level - { - destinationid = getMatchingGate(id); - if(destinationid == ObjectInstanceID()) //no exit - { - showInfoDialog(h,153,0);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged. - } - break; - } - } - if(destinationid == ObjectInstanceID()) - { - logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( "; - return; - } - if (ID == Obj::WHIRLPOOL) - { - std::set tiles = cb->getObj(destinationid)->getBlockedPos(); - auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()); - cb->moveHero(h->id, tile + int3(1,0,0), true); - } - else - cb->moveHero (h->id,CGHeroInstance::convertPosition(cb->getObj(destinationid)->pos,true) - getVisitableOffset(), true); -} - -void CGTeleport::initObj() -{ - int si = subID; - switch (ID) - { - case Obj::SUBTERRANEAN_GATE://ignore subterranean gates subid - case Obj::WHIRLPOOL: - { - si = 0; - break; - } - default: - break; - } - objs[ID][si].push_back(id); -} - -void CGTeleport::postInit() //matches subterranean gates into pairs -{ - //split on underground and surface gates - std::vector gatesSplit[2]; //surface and underground gates - for(auto & elem : objs[Obj::SUBTERRANEAN_GATE][0]) - { - const CGObjectInstance *hlp = cb->getObj(elem); - gatesSplit[hlp->pos.z].push_back(hlp); - } - - //sort by position - std::sort(gatesSplit[0].begin(), gatesSplit[0].end(), [](const CGObjectInstance * a, const CGObjectInstance * b) - { - return a->pos < b->pos; - }); - - for(size_t i = 0; i < gatesSplit[0].size(); i++) - { - const CGObjectInstance *cur = gatesSplit[0][i]; - - //find nearest underground exit - std::pair best(-1, std::numeric_limits::max()); //pair - for(int j = 0; j < gatesSplit[1].size(); j++) - { - const CGObjectInstance *checked = gatesSplit[1][j]; - if(!checked) - continue; - si32 hlp = checked->pos.dist2dSQ(cur->pos); - if(hlp < best.second) - { - best.first = j; - best.second = hlp; - } - } - - if(best.first >= 0) //found pair - { - gates.push_back(std::make_pair(cur->id, gatesSplit[1][best.first]->id)); - gatesSplit[1][best.first] = nullptr; - } - else - gates.push_back(std::make_pair(cur->id, ObjectInstanceID())); - } - objs.erase(Obj::SUBTERRANEAN_GATE); -} - -ObjectInstanceID CGTeleport::getMatchingGate(ObjectInstanceID id) -{ - for(auto & gate : gates) - { - if(gate.first == id) - return gate.second; - if(gate.second == id) - return gate.first; - } - - return ObjectInstanceID(); -} - -void CGArtifact::initObj() -{ - blockVisit = true; - if(ID == Obj::ARTIFACT) - { - hoverName = VLC->arth->artifacts[subID]->Name(); - if(!storedArtifact->artType) - storedArtifact->setType(VLC->arth->artifacts[subID]); - } - if(ID == Obj::SPELL_SCROLL) - subID = 1; - - assert(storedArtifact->artType); - assert(storedArtifact->getParentNodes().size()); - - //assert(storedArtifact->artType->id == subID); //this does not stop desync -} - -void CGArtifact::onHeroVisit( const CGHeroInstance * h ) const -{ - if(!stacksCount()) - { - InfoWindow iw; - iw.player = h->tempOwner; - switch(ID) - { - case Obj::ARTIFACT: - { - iw.soundID = soundBase::treasure; //play sound only for non-scroll arts - iw.components.push_back(Component(Component::ARTIFACT,subID,0,0)); - if(message.length()) - iw.text << message; - else - { - if (VLC->arth->artifacts[subID]->EventText().size()) - iw.text << std::pair (MetaString::ART_EVNTS, subID); - else //fix for mod artifacts with no event text - { - iw.text.addTxt (MetaString::ADVOB_TXT, 183); //% has found treasure - iw.text.addReplacement (h->name); - } - - } - } - break; - case Obj::SPELL_SCROLL: - { - int spellID = storedArtifact->getGivenSpellID(); - iw.components.push_back (Component(Component::SPELL, spellID,0,0)); - iw.text.addTxt (MetaString::ADVOB_TXT,135); - iw.text.addReplacement(MetaString::SPELL_NAME, spellID); - } - break; - } - cb->showInfoDialog(&iw); - pick(h); - } - else - { - if(message.size()) - { - BlockingDialog ynd(true,false); - ynd.player = h->getOwner(); - ynd.text << message; - cb->showBlockingDialog(&ynd); - } - else - { - blockingDialogAnswered(h, true); - } - } -} - -void CGArtifact::pick(const CGHeroInstance * h) const -{ - cb->giveHeroArtifact(h, storedArtifact, ArtifactPosition::FIRST_AVAILABLE); - cb->removeObject(this); -} - -void CGArtifact::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if(result.winner == 0) //attacker won - pick(hero); -} - -void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if(answer) - cb->startBattleI(hero, this); -} - -void CGPickable::initObj() -{ - blockVisit = true; - switch(ID) - { - case Obj::CAMPFIRE: - val2 = cb->gameState()->getRandomGenerator().nextInt(4, 6); - val1 = val2 * 100; - type = cb->gameState()->getRandomGenerator().nextInt(5); // given resource - break; - case Obj::FLOTSAM: - switch(type = cb->gameState()->getRandomGenerator().nextInt(3)) - { - case 0: - val1 = val2 = 0; - break; - case 1: - val1 = 5; - val2 = 0; - break; - case 2: - val1 = 5; - val2 = 200; - break; - case 3: - val1 = 10; - val2 = 500; - break; - } - break; - case Obj::SEA_CHEST: - { - int hlp = cb->gameState()->getRandomGenerator().nextInt(99); - if(hlp < 20) - { - val1 = 0; - type = 0; - } - else if(hlp < 90) - { - val1 = 1500; - type = 2; - } - else - { - val1 = 1000; - val2 = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_TREASURE); - type = 1; - } - } - break; - case Obj::SHIPWRECK_SURVIVOR: - { - int hlp = cb->gameState()->getRandomGenerator().nextInt(99); - if(hlp < 55) - val1 = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_TREASURE); - else if(hlp < 75) - val1 = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_MINOR); - else if(hlp < 95) - val1 = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_MAJOR); - else - val1 = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_RELIC); - } - break; - case Obj::TREASURE_CHEST: - { - int hlp = cb->gameState()->getRandomGenerator().nextInt(99); - if(hlp >= 95) - { - type = 1; - val1 = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_TREASURE); - return; - } - else if (hlp >= 65) - { - val1 = 2000; - } - else if(hlp >= 33) - { - val1 = 1500; - } - else - { - val1 = 1000; - } - - val2 = val1 - 500; - type = 0; - break; - } - } -} - -void CGPickable::onHeroVisit( const CGHeroInstance * h ) const -{ - switch(ID) - { - case Obj::CAMPFIRE: - { - cb->giveResource(h->tempOwner,static_cast(type),val2); //non-gold resource - cb->giveResource(h->tempOwner,Res::GOLD,val1);//gold - InfoWindow iw; - iw.soundID = soundBase::experience; - iw.player = h->tempOwner; - iw.components.push_back(Component(Component::RESOURCE,Res::GOLD,val1,0)); - iw.components.push_back(Component(Component::RESOURCE,type,val2,0)); - iw.text.addTxt(MetaString::ADVOB_TXT,23); - cb->showInfoDialog(&iw); - break; - } - case Obj::FLOTSAM: - { - cb->giveResource(h->tempOwner,Res::WOOD,val1); //wood - cb->giveResource(h->tempOwner,Res::GOLD,val2);//gold - InfoWindow iw; - iw.soundID = soundBase::GENIE; - iw.player = h->tempOwner; - if(val1) - iw.components.push_back(Component(Component::RESOURCE,Res::WOOD,val1,0)); - if(val2) - iw.components.push_back(Component(Component::RESOURCE,Res::GOLD,val2,0)); - - iw.text.addTxt(MetaString::ADVOB_TXT, 51+type); - cb->showInfoDialog(&iw); - break; - } - case Obj::SEA_CHEST: - { - InfoWindow iw; - iw.soundID = soundBase::chest; - iw.player = h->tempOwner; - iw.text.addTxt(MetaString::ADVOB_TXT, 116 + type); - - if(val1) //there is gold - { - iw.components.push_back(Component(Component::RESOURCE,Res::GOLD,val1,0)); - cb->giveResource(h->tempOwner,Res::GOLD,val1); - } - if(type == 1) //art - { - //TODO: what if no space in backpack? - iw.components.push_back(Component(Component::ARTIFACT, val2, 1, 0)); - iw.text.addReplacement(MetaString::ART_NAMES, val2); - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[val2],ArtifactPosition::FIRST_AVAILABLE); - } - cb->showInfoDialog(&iw); - break; - } - case Obj::SHIPWRECK_SURVIVOR: - { - InfoWindow iw; - iw.soundID = soundBase::experience; - iw.player = h->tempOwner; - iw.components.push_back(Component(Component::ARTIFACT,val1,1,0)); - iw.text.addTxt(MetaString::ADVOB_TXT, 125); - iw.text.addReplacement(MetaString::ART_NAMES, val1); - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[val1],ArtifactPosition::FIRST_AVAILABLE); - cb->showInfoDialog(&iw); - break; - } - case Obj::TREASURE_CHEST: - { - if (subID) //not OH3 treasure chest - { - logGlobal->warnStream() << "Not supported WoG treasure chest!"; - return; - } - - if(type) //there is an artifact - { - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[val1],ArtifactPosition::FIRST_AVAILABLE); - InfoWindow iw; - iw.soundID = soundBase::treasure; - iw.player = h->tempOwner; - iw.components.push_back(Component(Component::ARTIFACT,val1,1,0)); - iw.text.addTxt(MetaString::ADVOB_TXT,145); - iw.text.addReplacement(MetaString::ART_NAMES, val1); - cb->showInfoDialog(&iw); - break; - } - else - { - BlockingDialog sd(false,true); - sd.player = h->tempOwner; - sd.text.addTxt(MetaString::ADVOB_TXT,146); - sd.components.push_back(Component(Component::RESOURCE,Res::GOLD,val1,0)); - TExpType expVal = h->calculateXp(val2); - sd.components.push_back(Component(Component::EXPERIENCE,0,expVal, 0)); - sd.soundID = soundBase::chest; - cb->showBlockingDialog(&sd); - return; - } - } - } - cb->removeObject(this); -} - -void CGPickable::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - switch(answer) - { - case 1: //player pick gold - cb->giveResource(hero->tempOwner, Res::GOLD, val1); - break; - case 2: //player pick exp - cb->changePrimSkill(hero, PrimarySkill::EXPERIENCE, hero->calculateXp(val2)); - break; - default: - throw std::runtime_error("Unhandled treasure choice"); - } - cb->removeObject(this); -} - -bool CQuest::checkQuest (const CGHeroInstance * h) const -{ - switch (missionType) - { - case MISSION_NONE: - return true; - case MISSION_LEVEL: - if (m13489val <= h->level) - return true; - return false; - case MISSION_PRIMARY_STAT: - for (int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i) - { - if (h->getPrimSkillLevel(static_cast(i)) < m2stats[i]) - return false; - } - return true; - case MISSION_KILL_HERO: - case MISSION_KILL_CREATURE: - if (!h->cb->getObjByQuestIdentifier(m13489val)) - return true; - return false; - case MISSION_ART: - for (auto & elem : m5arts) - { - if (h->hasArt(elem)) - continue; - return false; //if the artifact was not found - } - return true; - case MISSION_ARMY: - { - std::vector::const_iterator cre; - TSlots::const_iterator it; - ui32 count; - for (cre = m6creatures.begin(); cre != m6creatures.end(); ++cre) - { - for (count = 0, it = h->Slots().begin(); it != h->Slots().end(); ++it) - { - if (it->second->type == cre->type) - count += it->second->count; - } - if (count < cre->count) //not enough creatures of this kind - return false; - } - } - return true; - case MISSION_RESOURCES: - for (Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, +1)) //including Mithril ? - { //Quest has no direct access to callback - if (h->cb->getResource (h->tempOwner, i) < m7resources[i]) - return false; - } - return true; - case MISSION_HERO: - if (m13489val == h->type->ID.getNum()) - return true; - return false; - case MISSION_PLAYER: - if (m13489val == h->getOwner().getNum()) - return true; - return false; - default: - return false; - } -} -void CQuest::getVisitText (MetaString &iwText, std::vector &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const -{ - std::string text; - bool failRequirements = (h ? !checkQuest(h) : true); - - if (firstVisit) - { - isCustom = isCustomFirst; - iwText << (text = firstVisitText); - } - else if (failRequirements) - { - isCustom = isCustomNext; - iwText << (text = nextVisitText); - } - switch (missionType) - { - case MISSION_LEVEL: - components.push_back(Component (Component::EXPERIENCE, 0, m13489val, 0)); - if (!isCustom) - iwText.addReplacement(m13489val); - break; - case MISSION_PRIMARY_STAT: - { - MetaString loot; - for (int i = 0; i < 4; ++i) - { - if (m2stats[i]) - { - components.push_back(Component (Component::PRIM_SKILL, i, m2stats[i], 0)); - loot << "%d %s"; - loot.addReplacement(m2stats[i]); - loot.addReplacement(VLC->generaltexth->primarySkillNames[i]); - } - } - if (!isCustom) - iwText.addReplacement(loot.buildList()); - } - break; - case MISSION_KILL_HERO: - components.push_back(Component(Component::HERO_PORTRAIT, heroPortrait, 0, 0)); - if (!isCustom) - addReplacements(iwText, text); - break; - case MISSION_HERO: - //FIXME: portrait may not match hero, if custom portrait was set in map editor - components.push_back(Component (Component::HERO_PORTRAIT, VLC->heroh->heroes[m13489val]->imageIndex, 0, 0)); - if (!isCustom) - iwText.addReplacement(VLC->heroh->heroes[m13489val]->name); - break; - case MISSION_KILL_CREATURE: - { - components.push_back(Component(stackToKill)); - if (!isCustom) - { - addReplacements(iwText, text); - } - } - break; - case MISSION_ART: - { - MetaString loot; - for (auto & elem : m5arts) - { - components.push_back(Component (Component::ARTIFACT, elem, 0, 0)); - loot << "%s"; - loot.addReplacement(MetaString::ART_NAMES, elem); - } - if (!isCustom) - iwText.addReplacement(loot.buildList()); - } - break; - case MISSION_ARMY: - { - MetaString loot; - for (auto & elem : m6creatures) - { - components.push_back(Component(elem)); - loot << "%s"; - loot.addReplacement(elem); - } - if (!isCustom) - iwText.addReplacement(loot.buildList()); - } - break; - case MISSION_RESOURCES: - { - MetaString loot; - for (int i = 0; i < 7; ++i) - { - if (m7resources[i]) - { - components.push_back(Component (Component::RESOURCE, i, m7resources[i], 0)); - loot << "%d %s"; - loot.addReplacement(m7resources[i]); - loot.addReplacement(MetaString::RES_NAMES, i); - } - } - if (!isCustom) - iwText.addReplacement(loot.buildList()); - } - break; - case MISSION_PLAYER: - components.push_back(Component (Component::FLAG, m13489val, 0, 0)); - if (!isCustom) - iwText.addReplacement(VLC->generaltexth->colors[m13489val]); - break; - } -} -void CQuest::getRolloverText (MetaString &ms, bool onHover) const -{ - if (onHover) - ms << "\n\n"; - - ms << VLC->generaltexth->quests[missionType-1][onHover ? 3 : 4][textOption]; - - switch (missionType) - { - case MISSION_LEVEL: - ms.addReplacement(m13489val); - break; - case MISSION_PRIMARY_STAT: - { - MetaString loot; - for (int i = 0; i < 4; ++i) - { - if (m2stats[i]) - { - loot << "%d %s"; - loot.addReplacement(m2stats[i]); - loot.addReplacement(VLC->generaltexth->primarySkillNames[i]); - } - } - ms.addReplacement(loot.buildList()); - } - break; - case MISSION_KILL_HERO: - ms.addReplacement(heroName); - break; - case MISSION_KILL_CREATURE: - ms.addReplacement(stackToKill); - break; - case MISSION_ART: - { - MetaString loot; - for (auto & elem : m5arts) - { - loot << "%s"; - loot.addReplacement(MetaString::ART_NAMES, elem); - } - ms.addReplacement(loot.buildList()); - } - break; - case MISSION_ARMY: - { - MetaString loot; - for (auto & elem : m6creatures) - { - loot << "%s"; - loot.addReplacement(elem); - } - ms.addReplacement(loot.buildList()); - } - break; - case MISSION_RESOURCES: - { - MetaString loot; - for (int i = 0; i < 7; ++i) - { - if (m7resources[i]) - { - loot << "%d %s"; - loot.addReplacement(m7resources[i]); - loot.addReplacement(MetaString::RES_NAMES, i); - } - } - ms.addReplacement(loot.buildList()); - } - break; - case MISSION_HERO: - ms.addReplacement(VLC->heroh->heroes[m13489val]->name); - break; - case MISSION_PLAYER: - ms.addReplacement(VLC->generaltexth->colors[m13489val]); - break; - default: - break; - } -} - -void CQuest::getCompletionText (MetaString &iwText, std::vector &components, bool isCustom, const CGHeroInstance * h) const -{ - iwText << completedText; - switch (missionType) - { - case CQuest::MISSION_LEVEL: - if (!isCustomComplete) - iwText.addReplacement(m13489val); - break; - case CQuest::MISSION_PRIMARY_STAT: - if (vstd::contains (completedText,'%')) //there's one case when there's nothing to replace - { - MetaString loot; - for (int i = 0; i < 4; ++i) - { - if (m2stats[i]) - { - loot << "%d %s"; - loot.addReplacement(m2stats[i]); - loot.addReplacement(VLC->generaltexth->primarySkillNames[i]); - } - } - if (!isCustomComplete) - iwText.addReplacement(loot.buildList()); - } - break; - case CQuest::MISSION_ART: - { - MetaString loot; - for (auto & elem : m5arts) - { - loot << "%s"; - loot.addReplacement(MetaString::ART_NAMES, elem); - } - if (!isCustomComplete) - iwText.addReplacement(loot.buildList()); - } - break; - case CQuest::MISSION_ARMY: - { - MetaString loot; - for (auto & elem : m6creatures) - { - loot << "%s"; - loot.addReplacement(elem); - } - if (!isCustomComplete) - iwText.addReplacement(loot.buildList()); - } - break; - case CQuest::MISSION_RESOURCES: - { - MetaString loot; - for (int i = 0; i < 7; ++i) - { - if (m7resources[i]) - { - loot << "%d %s"; - loot.addReplacement(m7resources[i]); - loot.addReplacement(MetaString::RES_NAMES, i); - } - } - if (!isCustomComplete) - iwText.addReplacement(loot.buildList()); - } - break; - case MISSION_KILL_HERO: - case MISSION_KILL_CREATURE: - if (!isCustomComplete) - addReplacements(iwText, completedText); - break; - case MISSION_HERO: - if (!isCustomComplete) - iwText.addReplacement(VLC->heroh->heroes[m13489val]->name); - break; - case MISSION_PLAYER: - if (!isCustomComplete) - iwText.addReplacement(VLC->generaltexth->colors[m13489val]); - break; - } -} -void CGSeerHut::setObjToKill() -{ - if (quest->missionType == CQuest::MISSION_KILL_CREATURE) - { - quest->stackToKill = getCreatureToKill(false)->getStack(SlotID(0)); //FIXME: stacks tend to disappear (desync?) on server :? - assert(quest->stackToKill.type); - quest->stackToKill.count = 0; //no count in info window - quest->stackDirection = checkDirection(); - } - else if (quest->missionType == CQuest::MISSION_KILL_HERO) - { - quest->heroName = getHeroToKill(false)->name; - quest->heroPortrait = getHeroToKill(false)->portrait; - } -} - -void CGSeerHut::init() -{ - seerName = *RandomGeneratorUtil::nextItem(VLC->generaltexth->seerNames, cb->gameState()->getRandomGenerator()); - quest->textOption = cb->gameState()->getRandomGenerator().nextInt(2); -} - -void CGSeerHut::initObj() -{ - init(); - - quest->progress = CQuest::NOT_ACTIVE; - if (quest->missionType) - { - if (!quest->isCustomFirst) - quest->firstVisitText = VLC->generaltexth->quests[quest->missionType-1][0][quest->textOption]; - if (!quest->isCustomNext) - quest->nextVisitText = VLC->generaltexth->quests[quest->missionType-1][1][quest->textOption]; - if (!quest->isCustomComplete) - quest->completedText = VLC->generaltexth->quests[quest->missionType-1][2][quest->textOption]; - } - else - { - quest->progress = CQuest::COMPLETE; - quest->firstVisitText = VLC->generaltexth->seerEmpty[quest->textOption]; - } - -} - -void CGSeerHut::getRolloverText (MetaString &text, bool onHover) const -{ - quest->getRolloverText (text, onHover);//TODO: simplify? - if (!onHover) - text.addReplacement(seerName); -} - -const std::string & CGSeerHut::getHoverText() const -{ - switch (ID) - { - case Obj::SEER_HUT: - if (quest->progress != CQuest::NOT_ACTIVE) - { - hoverName = VLC->generaltexth->allTexts[347]; - boost::algorithm::replace_first(hoverName,"%s", seerName); - } - else //just seer hut - hoverName = VLC->generaltexth->names[ID]; - break; - case Obj::QUEST_GUARD: - hoverName = VLC->generaltexth->names[ID]; - break; - default: - logGlobal->debugStream() << "unrecognized quest object"; - } - if (quest->progress & quest->missionType) //rollover when the quest is active - { - MetaString ms; - getRolloverText (ms, true); - hoverName += ms.toString(); - } - return hoverName; -} - -void CQuest::addReplacements(MetaString &out, const std::string &base) const -{ - switch(missionType) - { - case MISSION_KILL_CREATURE: - out.addReplacement(stackToKill); - if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster - { - out.addReplacement(VLC->generaltexth->arraytxt[147+stackDirection]); - } - break; - case MISSION_KILL_HERO: - out.addReplacement(heroName); - break; - } -} - -bool IQuestObject::checkQuest(const CGHeroInstance* h) const -{ - return quest->checkQuest(h); -} - -void IQuestObject::getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const -{ - quest->getVisitText (text,components, isCustom, FirstVisit, h); -} - -void CGSeerHut::getCompletionText(MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h) const -{ - quest->getCompletionText (text, components, isCustom, h); - switch (rewardType) - { - case EXPERIENCE: components.push_back(Component (Component::EXPERIENCE, 0, h->calculateXp(rVal), 0)); - break; - case MANA_POINTS: components.push_back(Component (Component::PRIM_SKILL, 5, rVal, 0)); - break; - case MORALE_BONUS: components.push_back(Component (Component::MORALE, 0, rVal, 0)); - break; - case LUCK_BONUS: components.push_back(Component (Component::LUCK, 0, rVal, 0)); - break; - case RESOURCES: components.push_back(Component (Component::RESOURCE, rID, rVal, 0)); - break; - case PRIMARY_SKILL: components.push_back(Component (Component::PRIM_SKILL, rID, rVal, 0)); - break; - case SECONDARY_SKILL: components.push_back(Component (Component::SEC_SKILL, rID, rVal, 0)); - break; - case ARTIFACT: components.push_back(Component (Component::ARTIFACT, rID, 0, 0)); - break; - case SPELL: components.push_back(Component (Component::SPELL, rID, 0, 0)); - break; - case CREATURE: components.push_back(Component (Component::CREATURE, rID, rVal, 0)); - break; - } -} - -void CGSeerHut::setPropertyDer (ui8 what, ui32 val) -{ - switch (what) - { - case 10: - quest->progress = static_cast(val); - break; - } -} -void CGSeerHut::newTurn() const -{ - if (quest->lastDay >= 0 && quest->lastDay < cb->getDate()-1) //time is up - { - cb->setObjProperty (id, 10, CQuest::COMPLETE); - } - -} -void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const -{ - InfoWindow iw; - iw.player = h->getOwner(); - if (quest->progress < CQuest::COMPLETE) - { - bool firstVisit = !quest->progress; - bool failRequirements = !checkQuest(h); - bool isCustom=false; - - if (firstVisit) - { - isCustom = quest->isCustomFirst; - cb->setObjProperty (id, 10, CQuest::IN_PROGRESS); - - AddQuest aq; - aq.quest = QuestInfo (quest, this, visitablePos()); - aq.player = h->tempOwner; - cb->sendAndApply (&aq); //TODO: merge with setObjProperty? - } - else if (failRequirements) - { - isCustom = quest->isCustomNext; - } - - if (firstVisit || failRequirements) - { - getVisitText (iw.text, iw.components, isCustom, firstVisit, h); - - cb->showInfoDialog(&iw); - } - if (!failRequirements) // propose completion, also on first visit - { - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::QUEST; - - getCompletionText (bd.text, bd.components, isCustom, h); - - cb->showBlockingDialog (&bd); - return; - } - } - else - { - iw.text << VLC->generaltexth->seerEmpty[quest->textOption]; - if (ID == Obj::SEER_HUT) - iw.text.addReplacement(seerName); - cb->showInfoDialog(&iw); - } -} -int CGSeerHut::checkDirection() const -{ - int3 cord = getCreatureToKill()->pos; - if ((double)cord.x/(double)cb->getMapSize().x < 0.34) //north - { - if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //northwest - return 8; - else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //north - return 1; - else //northeast - return 2; - } - else if ((double)cord.x/(double)cb->getMapSize().x < 0.67) //horizontal - { - if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //west - return 7; - else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //central - return 9; - else //east - return 3; - } - else //south - { - if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //southwest - return 6; - else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //south - return 5; - else //southeast - return 4; - } -} -void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const -{ - if (accept) - { - switch (quest->missionType) - { - case CQuest::MISSION_ART: - for (auto & elem : quest->m5arts) - { - cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false))); - } - break; - case CQuest::MISSION_ARMY: - cb->takeCreatures(h->id, quest->m6creatures); - break; - case CQuest::MISSION_RESOURCES: - for (int i = 0; i < 7; ++i) - { - cb->giveResource(h->getOwner(), static_cast(i), -quest->m7resources[i]); - } - break; - default: - break; - } - cb->setObjProperty (id, 10, CQuest::COMPLETE); //mission complete - completeQuest(h); //make sure to remove QuestGuard at the very end - } -} -void CGSeerHut::completeQuest (const CGHeroInstance * h) const //reward -{ - switch (rewardType) - { - case EXPERIENCE: - { - TExpType expVal = h->calculateXp(rVal); - cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false); - break; - } - case MANA_POINTS: - { - cb->setManaPoints(h->id, h->mana+rVal); - break; - } - case MORALE_BONUS: case LUCK_BONUS: - { - Bonus hb(Bonus::ONE_WEEK, (rewardType == 3 ? Bonus::MORALE : Bonus::LUCK), - Bonus::OBJECT, rVal, h->id.getNum(), "", -1); - GiveBonus gb; - gb.id = h->id.getNum(); - gb.bonus = hb; - cb->giveHeroBonus(&gb); - } - break; - case RESOURCES: - cb->giveResource(h->getOwner(), static_cast(rID), rVal); - break; - case PRIMARY_SKILL: - cb->changePrimSkill(h, static_cast(rID), rVal, false); - break; - case SECONDARY_SKILL: - cb->changeSecSkill(h, SecondarySkill(rID), rVal, false); - break; - case ARTIFACT: - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[rID],ArtifactPosition::FIRST_AVAILABLE); - break; - case SPELL: - { - std::set spell; - spell.insert (SpellID(rID)); - cb->changeSpells(h, true, spell); - } - break; - case CREATURE: - { - CCreatureSet creatures; - creatures.setCreature(SlotID(0), CreatureID(rID), rVal); - cb->giveCreatures(this, h, creatures, false); - } - break; - default: - break; - } -} - -const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const -{ - const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val); - if(allowNull && !o) - return nullptr; - assert(o && (o->ID == Obj::HERO || o->ID == Obj::PRISON)); - return static_cast(o); -} - -const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const -{ - const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val); - if(allowNull && !o) - return nullptr; - assert(o && o->ID == Obj::MONSTER); - return static_cast(o); -} - -void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - finishQuest(hero, answer); -} - -void CGQuestGuard::init() -{ - blockVisit = true; - quest->textOption = cb->gameState()->getRandomGenerator().nextInt(3, 5); -} -void CGQuestGuard::completeQuest(const CGHeroInstance *h) const -{ - cb->removeObject(this); -} -void CGWitchHut::initObj() -{ - ability = *RandomGeneratorUtil::nextItem(allowedAbilities, cb->gameState()->getRandomGenerator()); -} - -void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const -{ - InfoWindow iw; - iw.soundID = soundBase::gazebo; - iw.player = h->getOwner(); - if(!wasVisited(h->tempOwner)) - cb->setObjProperty(id, 10, h->tempOwner.getNum()); - ui32 txt_id; - if(h->getSecSkillLevel(SecondarySkill(ability))) //you already know this skill - { - txt_id =172; - } - else if(!h->canLearnSkill()) //already all skills slots used - { - txt_id = 173; - } - else //give sec skill - { - iw.components.push_back(Component(Component::SEC_SKILL, ability, 1, 0)); - txt_id = 171; - cb->changeSecSkill(h, SecondarySkill(ability), 1, true); - } - - iw.text.addTxt(MetaString::ADVOB_TXT,txt_id); - iw.text.addReplacement(MetaString::SEC_SKILL_NAME, ability); - cb->showInfoDialog(&iw); -} - -const std::string & CGWitchHut::getHoverText() const -{ - hoverName = VLC->generaltexth->names[ID]; - if(wasVisited(cb->getLocalPlayer())) - { - hoverName += "\n" + VLC->generaltexth->allTexts[356]; // + (learn %s) - boost::algorithm::replace_first(hoverName,"%s",VLC->generaltexth->skillName[ability]); - const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); - if(h && h->getSecSkillLevel(SecondarySkill(ability))) //hero knows that ability - hoverName += "\n\n" + VLC->generaltexth->allTexts[357]; // (Already learned) - } - return hoverName; -} - -bool CGBonusingObject::wasVisited (const CGHeroInstance * h) const -{ - return h->hasBonusFrom(Bonus::OBJECT, ID); -} - -void CGBonusingObject::onHeroVisit( const CGHeroInstance * h ) const -{ - bool visited = h->hasBonusFrom(Bonus::OBJECT,ID); - int messageID=0; - int bonusMove = 0; - ui32 descr_id = 0; - InfoWindow iw; - iw.player = h->tempOwner; - GiveBonus gbonus; - gbonus.id = h->id.getNum(); - gbonus.bonus.duration = Bonus::ONE_BATTLE; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - - bool second = false; - Bonus secondBonus; - - switch(ID) - { - case Obj::BUOY: - messageID = 21; - iw.soundID = soundBase::MORALE; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = +1; - descr_id = 94; - break; - case Obj::SWAN_POND: - messageID = 29; - iw.soundID = soundBase::LUCK; - gbonus.bonus.type = Bonus::LUCK; - gbonus.bonus.val = 2; - descr_id = 67; - bonusMove = -h->movement; - break; - case Obj::FAERIE_RING: - messageID = 49; - iw.soundID = soundBase::LUCK; - gbonus.bonus.type = Bonus::LUCK; - gbonus.bonus.val = 1; - descr_id = 71; - break; - case Obj::FOUNTAIN_OF_FORTUNE: - messageID = 55; - iw.soundID = soundBase::LUCK; - gbonus.bonus.type = Bonus::LUCK; - gbonus.bonus.val = cb->gameState()->getRandomGenerator().nextInt(-1, 3); - descr_id = 69; - gbonus.bdescr.addReplacement((gbonus.bonus.val<0 ? "-" : "+") + boost::lexical_cast(gbonus.bonus.val)); - break; - case Obj::IDOL_OF_FORTUNE: - messageID = 62; - iw.soundID = soundBase::experience; - - gbonus.bonus.val = 1; - descr_id = 68; - if(cb->getDate(Date::DAY_OF_WEEK) == 7) //7th day of week - { - gbonus.bonus.type = Bonus::MORALE; - second = true; - secondBonus = gbonus.bonus; - secondBonus.type = Bonus::LUCK; - } - else - { - gbonus.bonus.type = (cb->getDate(Date::DAY_OF_WEEK)%2) ? Bonus::LUCK : Bonus::MORALE; - } - break; - case Obj::MERMAID: - messageID = 83; - iw.soundID = soundBase::LUCK; - gbonus.bonus.type = Bonus::LUCK; - gbonus.bonus.val = 1; - descr_id = 72; - break; - case Obj::RALLY_FLAG: - iw.soundID = soundBase::MORALE; - messageID = 111; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = 1; - descr_id = 102; - - second = true; - secondBonus = gbonus.bonus; - secondBonus.type = Bonus::LUCK; - - bonusMove = 400; - break; - case Obj::OASIS: - iw.soundID = soundBase::MORALE; - messageID = 95; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = 1; - descr_id = 95; - bonusMove = 800; - break; - case Obj::TEMPLE: - messageID = 140; - iw.soundID = soundBase::temple; - gbonus.bonus.type = Bonus::MORALE; - if(cb->getDate(Date::DAY_OF_WEEK)==7) //sunday - { - gbonus.bonus.val = 2; - descr_id = 97; - } - else - { - gbonus.bonus.val = 1; - descr_id = 96; - } - break; - case Obj::WATERING_HOLE: - iw.soundID = soundBase::MORALE; - messageID = 166; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = 1; - descr_id = 100; - bonusMove = 400; - break; - case Obj::FOUNTAIN_OF_YOUTH: - iw.soundID = soundBase::MORALE; - messageID = 57; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = 1; - descr_id = 103; - bonusMove = 400; - break; - case Obj::STABLES: - iw.soundID = soundBase::STORE; - bool someUpgradeDone = false; - - for (auto i = h->Slots().begin(); i != h->Slots().end(); ++i) - { - if(i->second->type->idNumber == CreatureID::CAVALIER) - { - cb->changeStackType(StackLocation(h, i->first), VLC->creh->creatures[CreatureID::CHAMPION]); - someUpgradeDone = true; - } - } - if (someUpgradeDone) - { - messageID = 138; - iw.components.push_back(Component(Component::CREATURE,11,0,1)); - } - else - messageID = 137; - - gbonus.bonus.type = Bonus::LAND_MOVEMENT; - gbonus.bonus.val = 600; - bonusMove = 600; - gbonus.bonus.duration = Bonus::ONE_WEEK; - //gbonus.bdescr << std::pair(6, 100); - break; - } - if (descr_id != 0) - gbonus.bdescr.addTxt(MetaString::ARRAY_TXT,descr_id); - assert(messageID); - if(visited) - { - if(ID==Obj::RALLY_FLAG || ID==Obj::OASIS || ID==Obj::MERMAID || ID==Obj::STABLES) - messageID--; - else - messageID++; - } - else - { - //TODO: fix if second bonus val != main bonus val - if(gbonus.bonus.type == Bonus::MORALE || secondBonus.type == Bonus::MORALE) - iw.components.push_back(Component(Component::MORALE,0,gbonus.bonus.val,0)); - if(gbonus.bonus.type == Bonus::LUCK || secondBonus.type == Bonus::LUCK) - iw.components.push_back(Component(Component::LUCK,0,gbonus.bonus.val,0)); - cb->giveHeroBonus(&gbonus); - if(second) - { - gbonus.bonus = secondBonus; - cb->giveHeroBonus(&gbonus); - } - if(bonusMove) //swan pond - take all move points, stables - give move point this day - { - SetMovePoints smp; - smp.hid = h->id; - smp.val = h->movement + bonusMove; - cb->setMovePoints(&smp); - } - } - iw.text.addTxt(MetaString::ADVOB_TXT,messageID); - cb->showInfoDialog(&iw); -} - -const std::string & CGBonusingObject::getHoverText() const -{ - const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); - hoverName = VLC->generaltexth->names[ID]; - if(h) - { - bool visited = h->hasBonusFrom(Bonus::OBJECT,ID); - hoverName += " " + visitedTxt(visited); - } - return hoverName; -} - -void CGBonusingObject::initObj() -{ - if(ID == Obj::BUOY || ID == Obj::MERMAID) - { - blockVisit = true; - } -} - -void CGMagicSpring::setPropertyDer(ui8 what, ui32 val) -{ - CGVisitableOPW::setPropertyDer (what, val); //set visitable if applicable - if (what == ObjProperty::LEFT_VISITED) - { - if (visitedTile == RIGHT) - visited = true; //both field were used, object is not available this week - else - visitedTile = LEFT; - } - else if (what == ObjProperty::RIGHT_VISITED) - { - if (visitedTile == LEFT) - visited = true; - else - visitedTile = RIGHT; - } - else if (what == ObjProperty::LEFTRIGHT_CLEAR) - visitedTile = CLEAR; -} -std::vector CGMagicSpring::getVisitableOffsets() const -{ - std::vector visitableTiles; - - for(int y = 0; y < 6; y++) - for (int x = 0; x < 8; x++) //starting from left - if (appearance.isVisitableAt(x, y)) - visitableTiles.push_back (int3(x, y , 0)); - - return visitableTiles; -} - -int3 CGMagicSpring::getVisitableOffset() const -{ - //FIXME: this also should stop AI from passing through already visited spring, is that ok? - auto visitableTiles = getVisitableOffsets(); - - if (visitableTiles.size() < 2) - { - logGlobal->warnStream() << "Warning: Magic Spring should have at least two visitable offsets!"; - return int3(-1,-1,-1); - } - if (visited) - return int3(-1,-1,-1); - else - { - if (visitedTile == RIGHT) - return visitableTiles[0]; //visit the other one now - else if (visitedTile == LEFT) - return visitableTiles[1]; - else - return visitableTiles[0]; //only left one? - } -} - -void CGMagicSpring::onHeroVisit(const CGHeroInstance * h) const -{ - int messageID; - - if (!visited) - { - if (h->mana > h->manaLimit()) - messageID = 76; - else - { - messageID = 74; - cb->setManaPoints (h->id, 2 * h->manaLimit());//TODO: mark left or right tile visited - if (visitedTile) //visitng the second tile - cb->setObjProperty (id, ObjProperty::VISITED, true); - else - { - auto visitableTiles = getVisitableOffsets(); - assert (visitableTiles.size() >= 2); - if (h->getPosition() == pos - visitableTiles[0]) - cb->setObjProperty (id, ObjProperty::LEFT_VISITED, true); - else if (h->getPosition() == pos - visitableTiles[1]) - cb->setObjProperty (id, ObjProperty::RIGHT_VISITED, true); - else - logGlobal->warnStream() << "Warning: hero is not on any Magic Spring visitable offsets!"; - } - } - } - else - messageID = 75; - showInfoDialog(h,messageID,soundBase::GENIE); -} -void CGMagicSpring::newTurn() const -{ - CGVisitableOPW::newTurn(); - if (cb->getDate(Date::DAY_OF_WEEK) == 1) - { - cb->setObjProperty(id, ObjProperty::LEFTRIGHT_CLEAR, false); - } -} - - -const std::string & CGMagicSpring::getHoverText() const -{ - //TODO: change hover text depending on hovered tile - hoverName = VLC->generaltexth->names[ID] + " " + visitedTxt(visited); - return hoverName; -} - -void CGMagicWell::onHeroVisit( const CGHeroInstance * h ) const -{ - int message; - - if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Well today - { - message = 78;//"A second drink at the well in one day will not help you." - } - else if(h->mana < h->manaLimit()) - { - giveDummyBonus(h->id); - cb->setManaPoints(h->id,h->manaLimit()); - message = 77; - } - else - { - message = 79; - } - showInfoDialog(h,message,soundBase::faerie); -} - -const std::string & CGMagicWell::getHoverText() const -{ - getNameVis(hoverName); - return hoverName; -} - -void CGPandoraBox::initObj() -{ - blockVisit = (ID==Obj::PANDORAS_BOX); //block only if it's really pandora's box (events also derive from that class) - hasGuardians = stacks.size(); -} - -void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const -{ - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::QUEST; - bd.text.addTxt (MetaString::ADVOB_TXT, 14); - cb->showBlockingDialog (&bd); -} - -void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const -{ - cb->removeAfterVisit(this); - - InfoWindow iw; - iw.player = h->getOwner(); - - bool changesPrimSkill = false; - for (auto & elem : primskills) - { - if(elem) - { - changesPrimSkill = true; - break; - } - } - - if(gainedExp || changesPrimSkill || abilities.size()) - { - TExpType expVal = h->calculateXp(gainedExp); - //getText(iw,afterBattle,175,h); //wtf? - iw.text.addTxt(MetaString::ADVOB_TXT, 175); //%s learns something - iw.text.addReplacement(h->name); - - if(expVal) - iw.components.push_back(Component(Component::EXPERIENCE,0,expVal,0)); - for(int i=0; ishowInfoDialog(&iw); - - //give sec skills - for(int i=0; igetSecSkillLevel(abilities[i]); - - if( (curLev && curLev < abilityLevels[i]) || (h->canLearnSkill() )) - { - cb->changeSecSkill(h,abilities[i],abilityLevels[i],true); - } - } - - //give prim skills - for(int i=0; ichangePrimSkill(h,static_cast(i),primskills[i],false); - - assert(!cb->isVisitCoveredByAnotherQuery(this, h)); - - //give exp - if(expVal) - cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false); - } - - if(!cb->isVisitCoveredByAnotherQuery(this, h)) - giveContentsAfterExp(h); - //Otherwise continuation occurs via post-level-up callback. -} - -void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const -{ - bool hadGuardians = hasGuardians; //copy, because flag will be emptied after issuing first post-battle message - - std::string msg = message; //in case box is removed in the meantime - InfoWindow iw; - iw.player = h->getOwner(); - - if(spells.size()) - { - std::set spellsToGive; - iw.components.clear(); - if (spells.size() > 1) - { - iw.text.addTxt(MetaString::ADVOB_TXT, 188); //%s learns spells - } - else - { - iw.text.addTxt(MetaString::ADVOB_TXT, 184); //%s learns a spell - } - iw.text.addReplacement(h->name); - std::vector > * sp = &VLC->spellh->objects; - for(auto i=spells.cbegin(); i != spells.cend(); i++) - { - if ((*sp)[*i]->level <= h->getSecSkillLevel(SecondarySkill::WISDOM) + 2) //enough wisdom - { - iw.components.push_back(Component(Component::SPELL,*i,0,0)); - spellsToGive.insert(*i); - } - } - if(!spellsToGive.empty()) - { - cb->changeSpells(h,true,spellsToGive); - cb->showInfoDialog(&iw); - } - } - - if(manaDiff) - { - getText(iw,hadGuardians,manaDiff,176,177,h); - iw.components.push_back(Component(Component::PRIM_SKILL,5,manaDiff,0)); - cb->showInfoDialog(&iw); - cb->setManaPoints(h->id, h->mana + manaDiff); - } - - if(moraleDiff) - { - getText(iw,hadGuardians,moraleDiff,178,179,h); - iw.components.push_back(Component(Component::MORALE,0,moraleDiff,0)); - cb->showInfoDialog(&iw); - GiveBonus gb; - gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::MORALE,Bonus::OBJECT,moraleDiff,id.getNum(),""); - gb.id = h->id.getNum(); - cb->giveHeroBonus(&gb); - } - - if(luckDiff) - { - getText(iw,hadGuardians,luckDiff,180,181,h); - iw.components.push_back(Component(Component::LUCK,0,luckDiff,0)); - cb->showInfoDialog(&iw); - GiveBonus gb; - gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,luckDiff,id.getNum(),""); - gb.id = h->id.getNum(); - cb->giveHeroBonus(&gb); - } - - iw.components.clear(); - iw.text.clear(); - for(int i=0; ishowInfoDialog(&iw); - } - - iw.components.clear(); - iw.text.clear(); - for(int i=0; i 0) - iw.components.push_back(Component(Component::RESOURCE,i,resources[i],0)); - } - if(iw.components.size()) - { - getText(iw,hadGuardians,183,h); - cb->showInfoDialog(&iw); - } - - iw.components.clear(); - // getText(iw,afterBattle,183,h); - iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - iw.text.addReplacement(h->name); - for(auto & elem : artifacts) - { - iw.components.push_back(Component(Component::ARTIFACT,elem,0,0)); - if(iw.components.size() >= 14) - { - cb->showInfoDialog(&iw); - iw.components.clear(); - iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - once more? - iw.text.addReplacement(h->name); - } - } - if(iw.components.size()) - { - cb->showInfoDialog(&iw); - } - - for(int i=0; igiveResource(h->getOwner(),static_cast(i),resources[i]); - - for(auto & elem : artifacts) - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[elem],ArtifactPosition::FIRST_AVAILABLE); - - iw.components.clear(); - iw.text.clear(); - - if (creatures.Slots().size()) - { //this part is taken straight from creature bank - MetaString loot; - for(auto & elem : creatures.Slots()) - { //build list of joined creatures - iw.components.push_back(Component(*elem.second)); - loot << "%s"; - loot.addReplacement(*elem.second); - } - - if (creatures.Slots().size() == 1 && creatures.Slots().begin()->second->count == 1) - iw.text.addTxt(MetaString::ADVOB_TXT, 185); - else - iw.text.addTxt(MetaString::ADVOB_TXT, 186); - - iw.text.addReplacement(loot.buildList()); - iw.text.addReplacement(h->name); - - cb->showInfoDialog(&iw); - cb->giveCreatures(this, h, creatures, true); - } - if(!hasGuardians && msg.size()) - { - iw.text << msg; - cb->showInfoDialog(&iw); - } -} - -void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const -{ - if(afterBattle || !message.size()) - { - iw.text.addTxt(MetaString::ADVOB_TXT,text);//%s has lost treasure. - iw.text.addReplacement(h->name); - } - else - { - iw.text << message; - afterBattle = true; - } -} - -void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const -{ - iw.components.clear(); - iw.text.clear(); - if(afterBattle || !message.size()) - { - iw.text.addTxt(MetaString::ADVOB_TXT,val < 0 ? negative : positive); //%s's luck takes a turn for the worse / %s's luck increases - iw.text.addReplacement(h->name); - } - else - { - iw.text << message; - afterBattle = true; - } -} - -void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if(result.winner) - return; - - giveContentsUpToExp(hero); -} - -void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if (answer) - { - if (stacksCount() > 0) //if pandora's box is protected by army - { - showInfoDialog(hero,16,0); - cb->startBattleI(hero, this); //grants things after battle - } - else if (message.size() == 0 && resources.size() == 0 - && primskills.size() == 0 && abilities.size() == 0 - && abilityLevels.size() == 0 && artifacts.size() == 0 - && spells.size() == 0 && creatures.Slots().size() > 0 - && gainedExp == 0 && manaDiff == 0 && moraleDiff == 0 && luckDiff == 0) //if it gives nothing without battle - { - showInfoDialog(hero,15,0); - cb->removeObject(this); - } - else //if it gives something without battle - { - giveContentsUpToExp(hero); - } - } -} - -void CGPandoraBox::heroLevelUpDone(const CGHeroInstance *hero) const -{ - giveContentsAfterExp(hero); -} - -void CGEvent::onHeroVisit( const CGHeroInstance * h ) const -{ - if(!(availableFor & (1 << h->tempOwner.getNum()))) - return; - if(cb->getPlayerSettings(h->tempOwner)->playerID) - { - if(humanActivate) - activated(h); - } - else if(computerActivate) - activated(h); -} - -void CGEvent::activated( const CGHeroInstance * h ) const -{ - if(stacksCount() > 0) - { - InfoWindow iw; - iw.player = h->tempOwner; - if(message.size()) - iw.text << message; - else - iw.text.addTxt(MetaString::ADVOB_TXT, 16); - cb->showInfoDialog(&iw); - cb->startBattleI(h, this); - } - else - { - giveContentsUpToExp(h); - } -} - -void CGObservatory::onHeroVisit( const CGHeroInstance * h ) const -{ - InfoWindow iw; - iw.player = h->tempOwner; - switch (ID) - { - case Obj::REDWOOD_OBSERVATORY: - case Obj::PILLAR_OF_FIRE: - { - iw.soundID = soundBase::LIGHTHOUSE; - iw.text.addTxt(MetaString::ADVOB_TXT,98 + (ID==Obj::PILLAR_OF_FIRE)); - - FoWChange fw; - fw.player = h->tempOwner; - fw.mode = 1; - cb->getTilesInRange (fw.tiles, pos, 20, h->tempOwner, 1); - cb->sendAndApply (&fw); - break; - } - case Obj::COVER_OF_DARKNESS: - { - iw.text.addTxt (MetaString::ADVOB_TXT, 31); - hideTiles(h->tempOwner, 20); - break; - } - } - cb->showInfoDialog(&iw); -} - -void CGShrine::onHeroVisit( const CGHeroInstance * h ) const -{ - if(spell == SpellID::NONE) - { - logGlobal->errorStream() << "Not initialized shrine visited!"; - return; - } - - if(!wasVisited(h->tempOwner)) - cb->setObjProperty(id, 10, h->tempOwner.getNum()); - - InfoWindow iw; - iw.soundID = soundBase::temple; - iw.player = h->getOwner(); - iw.text.addTxt(MetaString::ADVOB_TXT,127 + ID - 88); - iw.text.addTxt(MetaString::SPELL_NAME,spell); - iw.text << "."; - - if(!h->getArt(ArtifactPosition::SPELLBOOK)) - { - iw.text.addTxt(MetaString::ADVOB_TXT,131); - } - else if(ID == Obj::SHRINE_OF_MAGIC_THOUGHT && !h->getSecSkillLevel(SecondarySkill::WISDOM)) //it's third level spell and hero doesn't have wisdom - { - iw.text.addTxt(MetaString::ADVOB_TXT,130); - } - else if(vstd::contains(h->spells,spell))//hero already knows the spell - { - iw.text.addTxt(MetaString::ADVOB_TXT,174); - } - else //give spell - { - std::set spells; - spells.insert(spell); - cb->changeSpells(h, true, spells); - - iw.components.push_back(Component(Component::SPELL,spell,0,0)); - } - - cb->showInfoDialog(&iw); -} - -void CGShrine::initObj() -{ - if(spell == SpellID::NONE) //spell not set - { - int level = ID-87; - std::vector possibilities; - cb->getAllowedSpells (possibilities, level); - - if(possibilities.empty()) - { - logGlobal->errorStream() << "Error: cannot init shrine, no allowed spells!"; - return; - } - - spell = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator()); - } -} - -const std::string & CGShrine::getHoverText() const -{ - hoverName = VLC->generaltexth->names[ID]; - if(wasVisited(cb->getCurrentPlayer())) //TODO: use local player, not current - { - hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s) - boost::algorithm::replace_first(hoverName,"%s", spell.toSpell()->name); - const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); - if(h && vstd::contains(h->spells,spell)) //hero knows that ability - hoverName += "\n\n" + VLC->generaltexth->allTexts[354]; // (Already learned) - } - return hoverName; -} - -void CGSignBottle::initObj() -{ - //if no text is set than we pick random from the predefined ones - if(message.empty()) - { - message = *RandomGeneratorUtil::nextItem(VLC->generaltexth->randsign, cb->gameState()->getRandomGenerator()); - } - - if(ID == Obj::OCEAN_BOTTLE) - { - blockVisit = true; - } -} - -void CGSignBottle::onHeroVisit( const CGHeroInstance * h ) const -{ - InfoWindow iw; - iw.soundID = soundBase::STORE; - iw.player = h->getOwner(); - iw.text << message; - cb->showInfoDialog(&iw); - - if(ID == Obj::OCEAN_BOTTLE) - cb->removeObject(this); -} - -//TODO: remove -//void CGScholar::giveAnyBonus( const CGHeroInstance * h ) const -//{ -// -//} - -void CGScholar::onHeroVisit( const CGHeroInstance * h ) const -{ - - EBonusType type = bonusType; - int bid = bonusID; - //check if the bonus if applicable, if not - give primary skill (always possible) - int ssl = h->getSecSkillLevel(SecondarySkill(bid)); //current sec skill level, used if bonusType == 1 - if((type == SECONDARY_SKILL - && ((ssl == 3) || (!ssl && !h->canLearnSkill()))) ////hero already has expert level in the skill or (don't know skill and doesn't have free slot) - || (type == SPELL && (!h->getArt(ArtifactPosition::SPELLBOOK) || vstd::contains(h->spells, (ui32) bid) - || ( SpellID(bid).toSpell()->level > h->getSecSkillLevel(SecondarySkill::WISDOM) + 2) - ))) //hero doesn't have a spellbook or already knows the spell or doesn't have Wisdom - { - type = PRIM_SKILL; - bid = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS - 1); - } - - InfoWindow iw; - iw.soundID = soundBase::gazebo; - iw.player = h->getOwner(); - iw.text.addTxt(MetaString::ADVOB_TXT,115); - - switch (type) - { - case PRIM_SKILL: - cb->changePrimSkill(h,static_cast(bid),+1); - iw.components.push_back(Component(Component::PRIM_SKILL,bid,+1,0)); - break; - case SECONDARY_SKILL: - cb->changeSecSkill(h,SecondarySkill(bid),+1); - iw.components.push_back(Component(Component::SEC_SKILL,bid,ssl+1,0)); - break; - case SPELL: - { - std::set hlp; - hlp.insert(SpellID(bid)); - cb->changeSpells(h,true,hlp); - iw.components.push_back(Component(Component::SPELL,bid,0,0)); - } - break; - default: - logGlobal->errorStream() << "Error: wrong bonus type (" << (int)type << ") for Scholar!\n"; - return; - } - - cb->showInfoDialog(&iw); - cb->removeObject(this); -} - -void CGScholar::initObj() -{ - blockVisit = true; - if(bonusType == RANDOM) - { - bonusType = static_cast(cb->gameState()->getRandomGenerator().nextInt(2)); - switch(bonusType) - { - case PRIM_SKILL: - bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS -1); - break; - case SECONDARY_SKILL: - bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::SKILL_QUANTITY -1); - break; - case SPELL: - std::vector possibilities; - for (int i = 1; i < 6; ++i) - cb->getAllowedSpells (possibilities, i); - bonusID = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator()); - break; - } - } -} - -void CGGarrison::onHeroVisit (const CGHeroInstance *h) const -{ - int ally = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); - if (!ally && stacksCount() > 0) { - //TODO: Find a way to apply magic garrison effects in battle. - cb->startBattleI(h, this); - return; - } - - //New owner. - if (!ally) - cb->setOwner(this, h->tempOwner); - - cb->showGarrisonDialog(id, h->id, removableUnits); -} - -ui8 CGGarrison::getPassableness() const -{ - if ( !stacksCount() )//empty - anyone can visit - return GameConstants::ALL_PLAYERS; - if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit - return 0; - - ui8 mask = 0; - TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner); - for(PlayerColor it : ts->players) - mask |= 1<getOwner(); - bd.text.addTxt(MetaString::ADVOB_TXT,161); - cb->showBlockingDialog(&bd); - return; - } - default: - logGlobal->errorStream() << "Error: Unknown object (" << ID <<") treated as CGOnceVisitable!"; - return; - } - - InfoWindow iw; - iw.soundID = sound; - iw.player = h->getOwner(); - - if(players.size()) //we have been already visited... - { - txtid++; - if(ID == Obj::WAGON) //wagon has extra text (for finding art) we need to omit - txtid++; - iw.text.addTxt(MetaString::ADVOB_TXT, txtid); - } - else //first visit - give bonus! - { - switch(artOrRes) - { - case 0: // first visit but empty - if (ID == Obj::CORPSE) - ++txtid; - else - txtid+=2; - iw.text.addTxt(MetaString::ADVOB_TXT, txtid); - break; - case 1: //art - iw.components.push_back(Component(Component::ARTIFACT,bonusType,0,0)); - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[bonusType],ArtifactPosition::FIRST_AVAILABLE); - iw.text.addTxt(MetaString::ADVOB_TXT, txtid); - if (ID == Obj::CORPSE) - { - iw.text << "%s"; - iw.text.addReplacement(MetaString::ART_NAMES, bonusType); - } - break; - case 2: //res - iw.text.addTxt(MetaString::ADVOB_TXT, txtid); - iw.components.push_back (Component(Component::RESOURCE, bonusType, bonusVal, 0)); - cb->giveResource(h->getOwner(), static_cast(bonusType), bonusVal); - break; - } - if(ID == Obj::WAGON && artOrRes == 1) - { - iw.text.localStrings.back().second++; - iw.text.addReplacement(MetaString::ART_NAMES, bonusType); - } - } - - cb->showInfoDialog(&iw); - cb->setObjProperty(id, 10, h->getOwner().getNum()); -} - -const std::string & CGOnceVisitable::getHoverText() const -{ - const bool visited = wasVisited(cb->getCurrentPlayer()); - hoverName = VLC->generaltexth->names[ID] + " " + visitedTxt(visited); - return hoverName; -} - -void CGOnceVisitable::initObj() -{ - switch(ID) - { - case Obj::CORPSE: - { - blockVisit = true; - int hlp = cb->gameState()->getRandomGenerator().nextInt(99); - if(hlp < 20) - { - artOrRes = 1; - bonusType = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_TREASURE | CArtifact::ART_MINOR | CArtifact::ART_MAJOR); - } - else - { - artOrRes = 0; - } - } - break; - - case Obj::LEAN_TO: - { - artOrRes = 2; - bonusType = cb->gameState()->getRandomGenerator().nextInt(5); //any basic resource without gold - bonusVal = cb->gameState()->getRandomGenerator().nextInt(1, 4); - } - break; - - case Obj::WARRIORS_TOMB: - { - artOrRes = 1; - - int hlp = cb->gameState()->getRandomGenerator().nextInt(99); - if(hlp < 30) - bonusType = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_TREASURE); - else if(hlp < 80) - bonusType = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_MINOR); - else if(hlp < 95) - bonusType = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_MAJOR); - else - bonusType = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_RELIC); - } - break; - - case Obj::WAGON: - { - int hlp = cb->gameState()->getRandomGenerator().nextInt(99); - - if(hlp < 10) - { - artOrRes = 0; // nothing... :( - } - else if(hlp < 50) //minor or treasure art - { - artOrRes = 1; - bonusType = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_TREASURE | CArtifact::ART_MINOR); - } - else //2 - 5 of non-gold resource - { - artOrRes = 2; - bonusType = cb->gameState()->getRandomGenerator().nextInt(5); - bonusVal = cb->gameState()->getRandomGenerator().nextInt(2, 5); - } - } - break; - } -} - -void CGOnceVisitable::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - //must have been Tomb - if(answer) - { - InfoWindow iw; - iw.player = hero->getOwner(); - iw.components.push_back(Component(Component::MORALE,0,-3,0)); - - if(players.size()) //we've been already visited, player found nothing - { - iw.text.addTxt(MetaString::ADVOB_TXT,163); - } - else //first visit - give artifact - { - iw.text.addTxt(MetaString::ADVOB_TXT,162); - iw.components.push_back(Component(Component::ARTIFACT,bonusType,0,0)); - iw.text.addReplacement(MetaString::ART_NAMES, bonusType); - - cb->giveHeroNewArtifact(hero, VLC->arth->artifacts[bonusType],ArtifactPosition::FIRST_AVAILABLE); - } - - if(!hero->hasBonusFrom(Bonus::OBJECT,ID)) //we don't have modifier from this object yet - { - //ruin morale - GiveBonus gb; - gb.id = hero->id.getNum(); - gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::MORALE,Bonus::OBJECT,-3,id.getNum(),""); - gb.bdescr.addTxt(MetaString::ARRAY_TXT,104); //Warrior Tomb Visited -3 - cb->giveHeroBonus(&gb); - } - cb->showInfoDialog(&iw); - cb->setObjProperty(id, 10, hero->getOwner().getNum()); - } -} - -void CBank::initObj() -{ - index = VLC->objh->bankObjToIndex(this); - bc = nullptr; - daycounter = 0; - multiplier = 1; -} -const std::string & CBank::getHoverText() const -{ - bool visited = (bc == nullptr); - hoverName = VLC->objh->creBanksNames[index] + " " + visitedTxt(visited); - return hoverName; -} -void CBank::reset(ui16 var1) //prevents desync -{ - ui8 chance = 0; - for (auto & elem : VLC->objh->banksInfo[index]) - { - if (var1 < (chance += elem->chance)) - { - bc = elem; - break; - } - } - artifacts.clear(); -} - -void CBank::initialize() const -{ - cb->setObjProperty(id, ObjProperty::BANK_RESET, cb->gameState()->getRandomGenerator().nextInt()); //synchronous reset - - for (ui8 i = 0; i <= 3; i++) - { - for (ui8 n = 0; n < bc->artifacts[i]; n++) - { - CArtifact::EartClass artClass; - switch(i) - { - case 0: artClass = CArtifact::ART_TREASURE; break; - case 1: artClass = CArtifact::ART_MINOR; break; - case 2: artClass = CArtifact::ART_MAJOR; break; - case 3: artClass = CArtifact::ART_RELIC; break; - default: assert(0); continue; - } - - int artID = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), artClass); - cb->setObjProperty(id, ObjProperty::BANK_ADD_ARTIFACT, artID); - } - } - - cb->setObjProperty(id, ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //get army -} -void CBank::setPropertyDer (ui8 what, ui32 val) -/// random values are passed as arguments and processed identically on all clients -{ - switch (what) - { - case ObjProperty::BANK_DAYCOUNTER: //daycounter - if (val == 0) - daycounter = 1; //yes, 1 - else - daycounter++; - break; - case ObjProperty::BANK_MULTIPLIER: //multiplier, in percent - multiplier = val / 100.0; - break; - case 13: //bank preset - bc = VLC->objh->banksInfo[index][val]; - break; - case ObjProperty::BANK_RESET: - reset (val%100); - break; - case ObjProperty::BANK_CLEAR_CONFIG: - bc = nullptr; - break; - case ObjProperty::BANK_CLEAR_ARTIFACTS: //remove rewards from Derelict Ship - artifacts.clear(); - break; - case ObjProperty::BANK_INIT_ARMY: //set ArmedInstance army - { - int upgraded = 0; - if (val%100 < bc->upgradeChance) //once again anti-desync - upgraded = 1; - switch (bc->guards.size()) - { - case 1: - for (int i = 0; i < 4; ++i) - setCreature (SlotID(i), bc->guards[0].first, bc->guards[0].second / 5 ); - setCreature (SlotID(4), CreatureID(bc->guards[0].first + upgraded), bc->guards[0].second / 5 ); - break; - case 4: - { - if (bc->guards.back().second) //all stacks are present - { - for (auto & elem : bc->guards) - { - setCreature (SlotID(stacksCount()), elem.first, elem.second); - } - } - else if (bc->guards[2].second)//Wraiths are present, split two stacks in Crypt - { - setCreature (SlotID(0), bc->guards[0].first, bc->guards[0].second / 2 ); - setCreature (SlotID(1), bc->guards[1].first, bc->guards[1].second / 2); - setCreature (SlotID(2), CreatureID(bc->guards[2].first + upgraded), bc->guards[2].second); - setCreature (SlotID(3), bc->guards[1].first, bc->guards[1].second / 2 ); - setCreature (SlotID(4), bc->guards[0].first, bc->guards[0].second - (bc->guards[0].second / 2) ); - - } - else //split both stacks - { - for (int i = 0; i < 3; ++i) //skellies - setCreature (SlotID(2*i), bc->guards[0].first, bc->guards[0].second / 3); - for (int i = 0; i < 2; ++i) //zombies - setCreature (SlotID(2*i+1), bc->guards[1].first, bc->guards[1].second / 2); - } - } - break; - default: - logGlobal->warnStream() << "Error: Unexpected army data: " << bc->guards.size() <<" items found"; - return; - } - } - break; - case ObjProperty::BANK_ADD_ARTIFACT: //add Artifact - { - artifacts.push_back (val); - break; - } - } -} - -void CBank::newTurn() const -{ - if (bc == nullptr) - { - if (cb->getDate() == 1) - initialize(); //initialize on first day - else if (daycounter >= 28 && (subID < 13 || subID > 16)) //no reset for Emissaries - { - initialize(); - cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 0); //daycounter 0 - if (ID == Obj::DERELICT_SHIP && cb->getDate() > 1) - { - cb->setObjProperty (id, ObjProperty::BANK_MULTIPLIER, 0);//ugly hack to make derelict ships usable only once - cb->setObjProperty (id, ObjProperty::BANK_CLEAR_ARTIFACTS, 0); - } - } - else - cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 1); //daycounter++ - } -} -bool CBank::wasVisited (PlayerColor player) const -{ - return !bc; -} - -void CBank::onHeroVisit (const CGHeroInstance * h) const -{ - if (bc) - { - int banktext = 0; - switch (ID) - { - case Obj::CREATURE_BANK: - banktext = 32; - break; - case Obj::DERELICT_SHIP: - banktext = 41; - break; - case Obj::DRAGON_UTOPIA: - banktext = 47; - break; - case Obj::CRYPT: - banktext = 119; - break; - case Obj::SHIPWRECK: - banktext = 122; - break; - } - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::ROGUE; - bd.text.addTxt(MetaString::ADVOB_TXT,banktext); - if (ID == Obj::CREATURE_BANK) - bd.text.addReplacement(VLC->objh->creBanksNames[index]); - cb->showBlockingDialog (&bd); - } - else - { - InfoWindow iw; - iw.soundID = soundBase::GRAVEYARD; - iw.player = h->getOwner(); - if (ID == Obj::CRYPT) //morale penalty for empty Crypt - { - GiveBonus gbonus; - gbonus.id = h->id.getNum(); - gbonus.bonus.duration = Bonus::ONE_BATTLE; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[98]; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = -1; - cb->giveHeroBonus(&gbonus); - iw.text << VLC->generaltexth->advobtxt[120]; - iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); - } - else - { - iw.text << VLC->generaltexth->advobtxt[33]; - iw.text.addReplacement(VLC->objh->creBanksNames[index]); - } - cb->showInfoDialog(&iw); - } -} - -void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if (result.winner == 0) - { - int textID = -1; - InfoWindow iw; - iw.player = hero->getOwner(); - MetaString loot; - - switch (ID) - { - case Obj::CREATURE_BANK: case Obj::DRAGON_UTOPIA: - textID = 34; - break; - case Obj::DERELICT_SHIP: - if (multiplier) - textID = 43; - else - { - GiveBonus gbonus; - gbonus.id = hero->id.getNum(); - gbonus.bonus.duration = Bonus::ONE_BATTLE; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[101]; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = -1; - cb->giveHeroBonus(&gbonus); - textID = 42; - iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); - } - break; - case Obj::CRYPT: - if (bc->resources.size() != 0) - textID = 121; - else - { - iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); - GiveBonus gbonus; - gbonus.id = hero->id.getNum(); - gbonus.bonus.duration = Bonus::ONE_BATTLE; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[ID]; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = -1; - cb->giveHeroBonus(&gbonus); - textID = 120; - iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); - } - break; - case Obj::SHIPWRECK: - if (bc->resources.size()) - textID = 124; - else - textID = 123; - break; - } - - //grant resources - if (textID != 42) //empty derelict ship gives no cash - { - for (int it = 0; it < bc->resources.size(); it++) - { - if (bc->resources[it] != 0) - { - iw.components.push_back (Component (Component::RESOURCE, it, bc->resources[it], 0)); - loot << "%d %s"; - loot.addReplacement(iw.components.back().val); - loot.addReplacement(MetaString::RES_NAMES, iw.components.back().subtype); - cb->giveResource (hero->getOwner(), static_cast(it), bc->resources[it]); - } - } - } - //grant artifacts - for (auto & elem : artifacts) - { - iw.components.push_back (Component (Component::ARTIFACT, elem, 0, 0)); - loot << "%s"; - loot.addReplacement(MetaString::ART_NAMES, elem); - cb->giveHeroNewArtifact (hero, VLC->arth->artifacts[elem], ArtifactPosition::FIRST_AVAILABLE); - } - //display loot - if (!iw.components.empty()) - { - iw.text.addTxt (MetaString::ADVOB_TXT, textID); - if (textID == 34) - { - iw.text.addReplacement(MetaString::CRE_PL_NAMES, result.casualties[1].begin()->first); - iw.text.addReplacement(loot.buildList()); - } - cb->showInfoDialog(&iw); - } - loot.clear(); - iw.components.clear(); - iw.text.clear(); - - //grant creatures - CCreatureSet ourArmy; - for (auto it = bc->creatures.cbegin(); it != bc->creatures.cend(); it++) - { - SlotID slot = ourArmy.getSlotFor(it->first); - ourArmy.addToSlot(slot, it->first, it->second); - } - for (auto & elem : ourArmy.Slots()) - { - iw.components.push_back(Component(*elem.second)); - loot << "%s"; - loot.addReplacement(*elem.second); - } - - if (ourArmy.Slots().size()) - { - if (ourArmy.Slots().size() == 1 && ourArmy.Slots().begin()->second->count == 1) - iw.text.addTxt (MetaString::ADVOB_TXT, 185); - else - iw.text.addTxt (MetaString::ADVOB_TXT, 186); - - iw.text.addReplacement(loot.buildList()); - iw.text.addReplacement(hero->name); - cb->showInfoDialog(&iw); - cb->giveCreatures(this, hero, ourArmy, false); - } - cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); //bc = nullptr - } -} - -void CBank::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if (answer) - { - cb->startBattleI(hero, this, true); - } -} - -void CGPyramid::initObj() -{ - std::vector available; - cb->getAllowedSpells (available, 5); - if (available.size()) - { - bc = VLC->objh->banksInfo[21].front(); //TODO: remove hardcoded value? - spell = *RandomGeneratorUtil::nextItem(available, cb->gameState()->getRandomGenerator()); - } - else - { - logGlobal->errorStream() <<"No spells available for Pyramid! Object set to empty."; - } - setPropertyDer(ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //set guards at game start -} -const std::string & CGPyramid::getHoverText() const -{ - hoverName = VLC->objh->creBanksNames[21]+ " " + visitedTxt((bc==nullptr)); - return hoverName; -} -void CGPyramid::onHeroVisit (const CGHeroInstance * h) const -{ - if (bc) - { - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::MYSTERY; - bd.text << VLC->generaltexth->advobtxt[105]; - cb->showBlockingDialog(&bd); - } - else - { - InfoWindow iw; - iw.player = h->getOwner(); - iw.text << VLC->generaltexth->advobtxt[107]; - iw.components.push_back (Component (Component::LUCK, 0 , -2, 0)); - GiveBonus gb; - gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,-2,id.getNum(),VLC->generaltexth->arraytxt[70]); - gb.id = h->id.getNum(); - cb->giveHeroBonus(&gb); - cb->showInfoDialog(&iw); - } -} - -void CGPyramid::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if (result.winner == 0) - { - InfoWindow iw; - iw.player = hero->getOwner(); - iw.text.addTxt (MetaString::ADVOB_TXT, 106); - iw.text.addTxt (MetaString::SPELL_NAME, spell); - if (!hero->getArt(ArtifactPosition::SPELLBOOK)) - iw.text.addTxt (MetaString::ADVOB_TXT, 109); //no spellbook - else if (hero->getSecSkillLevel(SecondarySkill::WISDOM) < 3) - iw.text.addTxt (MetaString::ADVOB_TXT, 108); //no expert Wisdom - else - { - std::set spells; - spells.insert (SpellID(spell)); - cb->changeSpells (hero, true, spells); - iw.components.push_back(Component (Component::SPELL, spell, 0, 0)); - } - cb->showInfoDialog(&iw); - cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); - } -} - -void CGKeys::setPropertyDer (ui8 what, ui32 val) //101-108 - enable key for player 1-8 -{ - if (what >= 101 && what <= (100 + PlayerColor::PLAYER_LIMIT_I)) - { - PlayerColor player(what-101); - playerKeyMap[player].insert((ui8)val); - } - else - logGlobal->errorStream() << boost::format("Unexpected properties requested to set: what=%d, val=%d") % (int)what % val; -} - -bool CGKeys::wasMyColorVisited (PlayerColor player) const -{ - if (vstd::contains(playerKeyMap[player], subID)) //creates set if it's not there - return true; - else - return false; -} - -const std::string& CGKeys::getHoverText() const -{ - bool visited = wasMyColorVisited (cb->getLocalPlayer()); - hoverName = getName() + "\n" + visitedTxt(visited); - return hoverName; -} - - -const std::string CGKeys::getName() const -{ - std::string name; - name = VLC->generaltexth->tentColors[subID] + " " + VLC->generaltexth->names[ID]; - return name; -} - -bool CGKeymasterTent::wasVisited (PlayerColor player) const -{ - return wasMyColorVisited (player); -} - -void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const -{ - int txt_id; - if (!wasMyColorVisited (h->getOwner()) ) - { - cb->setObjProperty(id, h->tempOwner.getNum()+101, subID); - txt_id=19; - } - else - txt_id=20; - showInfoDialog(h,txt_id,soundBase::CAVEHEAD); -} - -void CGBorderGuard::initObj() -{ - //ui32 m13489val = subID; //store color as quest info - blockVisit = true; -} - -void CGBorderGuard::getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const -{ - text << std::pair(11,18); -} - -void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const -{ - if (!onHover) - text << VLC->generaltexth->tentColors[subID] << " " << VLC->generaltexth->names[Obj::KEYMASTER]; -} - -bool CGBorderGuard::checkQuest (const CGHeroInstance * h) const -{ - return wasMyColorVisited (h->tempOwner); -} - -void CGBorderGuard::onHeroVisit( const CGHeroInstance * h ) const -{ - if (wasMyColorVisited (h->getOwner()) ) - { - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::QUEST; - bd.text.addTxt (MetaString::ADVOB_TXT, 17); - cb->showBlockingDialog (&bd); - } - else - { - showInfoDialog(h,18,soundBase::CAVEHEAD); - - AddQuest aq; - aq.quest = QuestInfo (quest, this, visitablePos()); - aq.player = h->tempOwner; - cb->sendAndApply (&aq); - //TODO: add this quest only once OR check for multiple instances later - } -} - -void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if (answer) - cb->removeObject(this); -} - -void CGBorderGate::onHeroVisit( const CGHeroInstance * h ) const //TODO: passability -{ - if (!wasMyColorVisited (h->getOwner()) ) - { - showInfoDialog(h,18,0); - - AddQuest aq; - aq.quest = QuestInfo (quest, this, visitablePos()); - aq.player = h->tempOwner; - cb->sendAndApply (&aq); - } -} - -ui8 CGBorderGate::getPassableness() const -{ - ui8 ret = 0; - for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) - ret |= wasMyColorVisited(PlayerColor(i))<tempOwner; - cv.focusTime = 2000; - - FoWChange fw; - fw.player = h->tempOwner; - fw.mode = 1; - - for(auto it : eyelist[subID]) - { - const CGObjectInstance *eye = cb->getObj(it); - - cb->getTilesInRange (fw.tiles, eye->pos, 10, h->tempOwner, 1); - cb->sendAndApply(&fw); - cv.pos = eye->pos; - - cb->sendAndApply(&cv); - } - cv.pos = h->getPosition(false); - cb->sendAndApply(&cv); - } - } - else if (ID == Obj::EYE_OF_MAGI) - { - showInfoDialog(h,48,soundBase::invalid); - } - -} -void CGBoat::initObj() -{ - hero = nullptr; -} - -void CGSirens::initObj() -{ - blockVisit = true; -} - -const std::string & CGSirens::getHoverText() const -{ - getNameVis(hoverName); - return hoverName; -} - -void CGSirens::onHeroVisit( const CGHeroInstance * h ) const -{ - InfoWindow iw; - iw.soundID = soundBase::DANGER; - iw.player = h->tempOwner; - if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Sirens - { - iw.text.addTxt(MetaString::ADVOB_TXT,133); - } - else - { - giveDummyBonus(h->id, Bonus::ONE_BATTLE); - TExpType xp = 0; - - for (auto i = h->Slots().begin(); i != h->Slots().end(); i++) - { - TQuantity drown = i->second->count * 0.3; - if(drown) - { - cb->changeStackCount(StackLocation(h, i->first), -drown); - xp += drown * i->second->type->valOfBonuses(Bonus::STACK_HEALTH); - } - } - - if(xp) - { - xp = h->calculateXp(xp); - iw.text.addTxt(MetaString::ADVOB_TXT,132); - iw.text.addReplacement(xp); - cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, xp, false); - } - else - { - iw.text.addTxt(MetaString::ADVOB_TXT,134); - } - } - cb->showInfoDialog(&iw); - -} - -//bool IShipyard::validLocation() const -//{ -// std::vector offsets; -// getOutOffsets(offsets); -// -// TerrainTile *tile; -// for(int i = 0; i < offsets.size(); i++) -// if((tile = IObjectInterface::cb->getTile(o->pos + offsets[i])) && tile->terType == TerrainTile::water) //tile is in the map and is water -// return true; -// return false; -//} - -int3 IBoatGenerator::bestLocation() const -{ - std::vector offsets; - getOutOffsets(offsets); - - for (auto & offset : offsets) - { - if (const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map - { - if (tile->terType == ETerrainType::WATER && (!tile->blocked || tile->blockingObjects.front()->ID == 8)) //and is water and is not blocked or is blocked by boat - return o->pos + offset; - } - } - return int3 (-1,-1,-1); -} - -IBoatGenerator::EGeneratorState IBoatGenerator::shipyardStatus() const -{ - int3 tile = bestLocation(); - const TerrainTile *t = IObjectInterface::cb->getTile(tile); - if(!t) - return TILE_BLOCKED; //no available water - else if(!t->blockingObjects.size()) - return GOOD; //OK - else if(t->blockingObjects.front()->ID == Obj::BOAT) - return BOAT_ALREADY_BUILT; //blocked with boat - else - return TILE_BLOCKED; //blocked -} - -int IBoatGenerator::getBoatType() const -{ - //We make good ships by default - return 1; -} - - -IBoatGenerator::IBoatGenerator(const CGObjectInstance *O) -: o(O) -{ -} - -void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visitor) const -{ - switch(shipyardStatus()) - { - case BOAT_ALREADY_BUILT: - out.addTxt(MetaString::GENERAL_TXT, 51); - break; - case TILE_BLOCKED: - if(visitor) - { - out.addTxt(MetaString::GENERAL_TXT, 134); - out.addReplacement(visitor->name); - } - else - out.addTxt(MetaString::ADVOB_TXT, 189); - break; - case NO_WATER: - logGlobal->errorStream() << "Shipyard without water!!! " << o->pos << "\t" << o->id; - return; - } -} - -void IShipyard::getBoatCost( std::vector &cost ) const -{ - cost.resize(GameConstants::RESOURCE_QUANTITY); - cost[Res::WOOD] = 10; - cost[Res::GOLD] = 1000; -} - -IShipyard::IShipyard(const CGObjectInstance *O) - : IBoatGenerator(O) -{ -} - -IShipyard * IShipyard::castFrom( CGObjectInstance *obj ) -{ - if(!obj) - return nullptr; - - if(obj->ID == Obj::TOWN) - { - return static_cast(obj); - } - else if(obj->ID == Obj::SHIPYARD) - { - return static_cast(obj); - } - else - { - return nullptr; - } -} - -const IShipyard * IShipyard::castFrom( const CGObjectInstance *obj ) -{ - return castFrom(const_cast(obj)); -} - -CGShipyard::CGShipyard() - :IShipyard(this) -{ -} - -void CGShipyard::getOutOffsets( std::vector &offsets ) const -{ - // H J L K I - // A x S x B - // C E G F D - offsets += int3(-3,0,0), int3(1,0,0), //AB - int3(-3,1,0), int3(1,1,0), int3(-2,1,0), int3(0,1,0), int3(-1,1,0), //CDEFG - int3(-3,-1,0), int3(1,-1,0), int3(-2,-1,0), int3(0,-1,0), int3(-1,-1,0); //HIJKL -} - -void CGShipyard::onHeroVisit( const CGHeroInstance * h ) const -{ - if(!cb->gameState()->getPlayerRelations(tempOwner, h->tempOwner)) - cb->setOwner(this, h->tempOwner); - - auto s = shipyardStatus(); - if(s != IBoatGenerator::GOOD) - { - InfoWindow iw; - iw.player = tempOwner; - getProblemText(iw.text, h); - cb->showInfoDialog(&iw); - } - else - { - openWindow(OpenWindow::SHIPYARD_WINDOW,id.getNum(),h->id.getNum()); - } -} - -void CCartographer::onHeroVisit( const CGHeroInstance * h ) const -{ - if (!wasVisited (h->getOwner()) ) //if hero has not visited yet this cartographer - { - if (cb->getResource(h->tempOwner, Res::GOLD) >= 1000) //if he can afford a map - { - //ask if he wants to buy one - int text=0; - switch (subID) - { - case 0: - text = 25; - break; - case 1: - text = 26; - break; - case 2: - text = 27; - break; - default: - logGlobal->warnStream() << "Unrecognized subtype of cartographer"; - } - assert(text); - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::LIGHTHOUSE; - bd.text.addTxt (MetaString::ADVOB_TXT, text); - cb->showBlockingDialog (&bd); - } - else //if he cannot afford - { - showInfoDialog(h,28,soundBase::CAVEHEAD); - } - } - else //if he already visited carographer - { - showInfoDialog(h,24,soundBase::CAVEHEAD); - } -} - -void CCartographer::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if (answer) //if hero wants to buy map - { - cb->giveResource (hero->tempOwner, Res::GOLD, -1000); - FoWChange fw; - fw.mode = 1; - fw.player = hero->tempOwner; - - //subIDs of different types of cartographers: - //water = 0; land = 1; underground = 2; - cb->getAllTiles (fw.tiles, hero->tempOwner, subID - 1, !subID + 1); //reveal appropriate tiles - cb->sendAndApply (&fw); - cb->setObjProperty (id, 10, hero->tempOwner.getNum()); - } -} - -void CGDenOfthieves::onHeroVisit (const CGHeroInstance * h) const -{ - cb->showThievesGuildWindow(h->tempOwner, id); -} - -void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const -{ - InfoWindow iw; - iw.player = h->tempOwner; - TeamState *ts = cb->gameState()->getPlayerTeam(h->tempOwner); - assert(ts); - TeamID team = ts->id; - - if(!wasVisited(team)) - { - iw.text.addTxt(MetaString::ADVOB_TXT, 96); - cb->sendAndApply(&iw); - - cb->setObjProperty(id, 20, h->tempOwner.getNum()); //increment general visited obelisks counter - - openWindow(OpenWindow::PUZZLE_MAP, h->tempOwner.getNum()); - - cb->setObjProperty(id, 10, h->tempOwner.getNum()); //mark that particular obelisk as visited - } - else - { - iw.text.addTxt(MetaString::ADVOB_TXT, 97); - cb->sendAndApply(&iw); - } - -} - -void CGObelisk::initObj() -{ - obeliskCount++; -} - -const std::string & CGObelisk::getHoverText() const -{ - bool visited = wasVisited(cb->getLocalPlayer()); - hoverName = VLC->generaltexth->names[ID] + " " + visitedTxt(visited); - return hoverName; -} - -void CGObelisk::setPropertyDer( ui8 what, ui32 val ) -{ - CPlayersVisited::setPropertyDer(what, val); - switch(what) - { - case 20: - assert(val < PlayerColor::PLAYER_LIMIT_I); - visited[TeamID(val)]++; - - if(visited[TeamID(val)] > obeliskCount) - { - logGlobal->errorStream() << "Error: Visited " << visited[TeamID(val)] << "\t\t" << obeliskCount; - assert(0); - } - - break; - } -} - -void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const -{ - if(h->tempOwner != tempOwner) - { - PlayerColor oldOwner = tempOwner; - cb->setOwner(this,h->tempOwner); //not ours? flag it! - showInfoDialog(h,69,soundBase::LIGHTHOUSE); - giveBonusTo(h->tempOwner); - - if(oldOwner < PlayerColor::PLAYER_LIMIT) //remove bonus from old owner - { - RemoveBonus rb(RemoveBonus::PLAYER); - rb.whoID = oldOwner.getNum(); - rb.source = Bonus::OBJECT; - rb.id = id.getNum(); - cb->sendAndApply(&rb); - } - } -} - -void CGLighthouse::initObj() -{ - if(tempOwner < PlayerColor::PLAYER_LIMIT) - { - giveBonusTo(tempOwner); - } -} - -const std::string & CGLighthouse::getHoverText() const -{ - hoverName = VLC->generaltexth->names[ID]; - //TODO: owned by %s player - return hoverName; -} - -void CGLighthouse::giveBonusTo( PlayerColor player ) const -{ - GiveBonus gb(GiveBonus::PLAYER); - gb.bonus.type = Bonus::SEA_MOVEMENT; - gb.bonus.val = 500; - gb.id = player.getNum(); - gb.bonus.duration = Bonus::PERMANENT; - gb.bonus.source = Bonus::OBJECT; - gb.bonus.sid = id.getNum(); - cb->sendAndApply(&gb); -} - -void CArmedInstance::randomizeArmy(int type) -{ - for (auto & elem : stacks) - { - int & randID = elem.second->idRand; - if(randID >= 0) - { - int level = randID / 2; - bool upgrade = randID % 2; - elem.second->setType(VLC->townh->factions[type]->town->creatures[level][upgrade]); - - randID = -1; - } - assert(elem.second->valid(false)); - assert(elem.second->armyObj == this); - } - return; -} - -CArmedInstance::CArmedInstance() -{ - battle = nullptr; -} - -//int CArmedInstance::valOfGlobalBonuses(CSelector selector) const -//{ -//// if (tempOwner != NEUTRAL_PLAYER) -// return cb->gameState()->players[tempOwner].valOfBonuses(selector); -//} - -void CArmedInstance::updateMoraleBonusFromArmy() -{ - if(!validTypes(false)) //object not randomized, don't bother - return; - - Bonus *b = getBonusList().getFirst(Selector::sourceType(Bonus::ARMY).And(Selector::type(Bonus::MORALE))); - if(!b) - { - b = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1); - addNewBonus(b); - } - - //number of alignments and presence of undead - std::set factions; - bool hasUndead = false; - - for(auto slot : Slots()) - { - const CStackInstance * inst = slot.second; - const CCreature * creature = VLC->creh->creatures[inst->getCreatureID()]; - - factions.insert(creature->faction); - // Check for undead flag instead of faction (undead mummies are neutral) - hasUndead |= inst->hasBonusOfType(Bonus::UNDEAD); - } - - size_t factionsInArmy = factions.size(); //town garrison seems to take both sets into account - - // Take Angelic Alliance troop-mixing freedom of non-evil units into account. - if (hasBonusOfType(Bonus::NONEVIL_ALIGNMENT_MIX)) - { - size_t mixableFactions = 0; - - for(TFaction f : factions) - { - if (VLC->townh->factions[f]->alignment != EAlignment::EVIL) - mixableFactions++; - } - if (mixableFactions > 0) - factionsInArmy -= mixableFactions - 1; - } - - if(factionsInArmy == 1) - { - b->val = +1; - b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1 - } - else if (!factions.empty()) // no bonus from empty garrison - { - b->val = 2 - factionsInArmy; - b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d - } - boost::algorithm::trim(b->description); - - //-1 modifier for any Undead unit in army - const ui8 UNDEAD_MODIFIER_ID = -2; - Bonus *undeadModifier = getBonusList().getFirst(Selector::source(Bonus::ARMY, UNDEAD_MODIFIER_ID)); - if(hasUndead) - { - if(!undeadModifier) - addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116])); - } - else if(undeadModifier) - removeBonus(undeadModifier); - -} - -void CArmedInstance::armyChanged() -{ - updateMoraleBonusFromArmy(); -} - -CBonusSystemNode * CArmedInstance::whereShouldBeAttached(CGameState *gs) -{ - if(tempOwner < PlayerColor::PLAYER_LIMIT) - return gs->getPlayer(tempOwner); - else - return &gs->globalEffects; -} - -CBonusSystemNode * CArmedInstance::whatShouldBeAttached() -{ - return this; -} - -bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode::EMarketMode mode) const -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - { - double effectiveness = std::min((getMarketEfficiency() + 1.0) / 20.0, 0.5); - - double r = VLC->objh->resVals[id1], //value of given resource - g = VLC->objh->resVals[id2] / effectiveness; //value of wanted resource - - if(r>g) //if given resource is more expensive than wanted - { - val2 = ceil(r / g); - val1 = 1; - } - else //if wanted resource is more expensive - { - val1 = (g / r) + 0.5; - val2 = 1; - } - } - break; - case EMarketMode::CREATURE_RESOURCE: - { - const double effectivenessArray[] = {0.0, 0.3, 0.45, 0.50, 0.65, 0.7, 0.85, 0.9, 1.0}; - double effectiveness = effectivenessArray[std::min(getMarketEfficiency(), 8)]; - - double r = VLC->creh->creatures[id1]->cost[6], //value of given creature in gold - g = VLC->objh->resVals[id2] / effectiveness; //value of wanted resource - - if(r>g) //if given resource is more expensive than wanted - { - val2 = ceil(r / g); - val1 = 1; - } - else //if wanted resource is more expensive - { - val1 = (g / r) + 0.5; - val2 = 1; - } - } - break; - case EMarketMode::RESOURCE_PLAYER: - val1 = 1; - val2 = 1; - break; - case EMarketMode::RESOURCE_ARTIFACT: - { - double effectiveness = std::min((getMarketEfficiency() + 3.0) / 20.0, 0.6); - double r = VLC->objh->resVals[id1], //value of offered resource - g = VLC->arth->artifacts[id2]->price / effectiveness; //value of bought artifact in gold - - if(id1 != 6) //non-gold prices are doubled - r /= 2; - - val1 = std::max(1, (int)((g / r) + 0.5)); //don't sell arts for less than 1 resource - val2 = 1; - } - break; - case EMarketMode::ARTIFACT_RESOURCE: - { - double effectiveness = std::min((getMarketEfficiency() + 3.0) / 20.0, 0.6); - double r = VLC->arth->artifacts[id1]->price * effectiveness, - g = VLC->objh->resVals[id2]; - -// if(id2 != 6) //non-gold prices are doubled -// r /= 2; - - val1 = 1; - val2 = std::max(1, (int)((r / g) + 0.5)); //at least one resource is given in return - } - break; - case EMarketMode::CREATURE_EXP: - { - val1 = 1; - val2 = (VLC->creh->creatures[id1]->AIValue / 40) * 5; - } - break; - case EMarketMode::ARTIFACT_EXP: - { - val1 = 1; - - int givenClass = VLC->arth->artifacts[id1]->getArtClassSerial(); - if(givenClass < 0 || givenClass > 3) - { - val2 = 0; - return false; - } - - static const int expPerClass[] = {1000, 1500, 3000, 6000}; - val2 = expPerClass[givenClass]; - } - break; - default: - assert(0); - return false; - } - - return true; -} - -bool IMarket::allowsTrade(EMarketMode::EMarketMode mode) const -{ - return false; -} - -int IMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::ARTIFACT_RESOURCE: - case EMarketMode::CREATURE_RESOURCE: - return -1; - default: - return 1; - } -} - -std::vector IMarket::availableItemsIds(EMarketMode::EMarketMode mode) const -{ - std::vector ret; - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::ARTIFACT_RESOURCE: - case EMarketMode::CREATURE_RESOURCE: - for (int i = 0; i < 7; i++) - ret.push_back(i); - } - return ret; -} - -const IMarket * IMarket::castFrom(const CGObjectInstance *obj, bool verbose /*= true*/) -{ - switch(obj->ID) - { - case Obj::TOWN: - return static_cast(obj); - case Obj::ALTAR_OF_SACRIFICE: - case Obj::BLACK_MARKET: - case Obj::TRADING_POST: - case Obj::TRADING_POST_SNOW: - case Obj::FREELANCERS_GUILD: - return static_cast(obj); - case Obj::UNIVERSITY: - return static_cast(obj); - default: - if(verbose) - logGlobal->errorStream() << "Cannot cast to IMarket object with ID " << obj->ID; - return nullptr; - } -} - -IMarket::IMarket(const CGObjectInstance *O) - :o(O) -{ - -} - -std::vector IMarket::availableModes() const -{ - std::vector ret; - for (int i = 0; i < EMarketMode::MARTKET_AFTER_LAST_PLACEHOLDER; i++) - if(allowsTrade((EMarketMode::EMarketMode)i)) - ret.push_back((EMarketMode::EMarketMode)i); - - return ret; -} - -void CGMarket::onHeroVisit(const CGHeroInstance * h) const -{ - openWindow(OpenWindow::MARKET_WINDOW,id.getNum(),h->id.getNum()); -} - -int CGMarket::getMarketEfficiency() const -{ - return 5; -} - -bool CGMarket::allowsTrade(EMarketMode::EMarketMode mode) const -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::RESOURCE_PLAYER: - switch(ID) - { - case Obj::TRADING_POST: - case Obj::TRADING_POST_SNOW: - return true; - default: - return false; - } - case EMarketMode::CREATURE_RESOURCE: - return ID == Obj::FREELANCERS_GUILD; - //case ARTIFACT_RESOURCE: - case EMarketMode::RESOURCE_ARTIFACT: - return ID == Obj::BLACK_MARKET; - case EMarketMode::ARTIFACT_EXP: - case EMarketMode::CREATURE_EXP: - return ID == Obj::ALTAR_OF_SACRIFICE; //TODO? check here for alignment of visiting hero? - would not be coherent with other checks here - case EMarketMode::RESOURCE_SKILL: - return ID == Obj::UNIVERSITY; - default: - return false; - } -} - -int CGMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const -{ - return -1; -} - -std::vector CGMarket::availableItemsIds(EMarketMode::EMarketMode mode) const -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::RESOURCE_PLAYER: - return IMarket::availableItemsIds(mode); - default: - return std::vector(); - } -} - -CGMarket::CGMarket() - :IMarket(this) -{ -} - -std::vector CGBlackMarket::availableItemsIds(EMarketMode::EMarketMode mode) const -{ - switch(mode) - { - case EMarketMode::ARTIFACT_RESOURCE: - return IMarket::availableItemsIds(mode); - case EMarketMode::RESOURCE_ARTIFACT: - { - std::vector ret; - for(const CArtifact *a : artifacts) - if(a) - ret.push_back(a->id); - else - ret.push_back(-1); - return ret; - } - default: - return std::vector(); - } -} - -void CGBlackMarket::newTurn() const -{ - if(cb->getDate(Date::DAY_OF_MONTH) != 1) //new month - return; - - SetAvailableArtifacts saa; - saa.id = id.getNum(); - cb->pickAllowedArtsSet(saa.arts); - cb->sendAndApply(&saa); -} - -void CGUniversity::initObj() -{ - std::vector toChoose; - for(int i = 0; i < GameConstants::SKILL_QUANTITY; ++i) - { - if(cb->isAllowed(2, i)) - { - toChoose.push_back(i); - } - } - if(toChoose.size() < 4) - { - logGlobal->warnStream()<<"Warning: less then 4 available skills was found by University initializer!"; - return; - } - - // get 4 skills - for(int i = 0; i < 4; ++i) - { - // move randomly one skill to selected and remove from list - auto it = RandomGeneratorUtil::nextItem(toChoose, cb->gameState()->getRandomGenerator()); - skills.push_back(*it); - toChoose.erase(it); - } -} - -std::vector CGUniversity::availableItemsIds(EMarketMode::EMarketMode mode) const -{ - switch (mode) - { - case EMarketMode::RESOURCE_SKILL: - return skills; - - default: - return std::vector (); - } -} - -void CGUniversity::onHeroVisit(const CGHeroInstance * h) const -{ - openWindow(OpenWindow::UNIVERSITY_WINDOW,id.getNum(),h->id.getNum()); -} - -GrowthInfo::Entry::Entry(const std::string &format, int _count) - : count(_count) -{ - description = boost::str(boost::format(format) % count); -} - -GrowthInfo::Entry::Entry(int subID, BuildingID building, int _count) - : count(_count) -{ - description = boost::str(boost::format("%s %+d") % VLC->townh->factions[subID]->town->buildings.at(building)->Name() % count); -} - -CTownAndVisitingHero::CTownAndVisitingHero() -{ - setNodeType(TOWN_AND_VISITOR); -} - -int GrowthInfo::totalGrowth() const -{ - int ret = 0; - for(const Entry &entry : entries) - ret += entry.count; - - return ret; -} diff --git a/lib/CObjectHandler.h b/lib/CObjectHandler.h deleted file mode 100644 index 2d7fb4f10..000000000 --- a/lib/CObjectHandler.h +++ /dev/null @@ -1,1533 +0,0 @@ -#pragma once - -#include "../lib/CCreatureSet.h" -#include "../lib/CTownHandler.h" -#include "../lib/CDefObjInfoHandler.h" -#include "CArtHandler.h" -#include "../lib/ConstTransitivePtr.h" -#include "int3.h" -#include "GameConstants.h" -#include "ResourceSet.h" -#include "CRandomGenerator.h" - -/* - * CObjectHandler.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 - * - */ - -class CGameState; -class CArtifactInstance; -struct MetaString; -struct BattleInfo; -struct QuestInfo; -class IGameCallback; -struct BattleResult; -class CGObjectInstance; -class CScript; -class CObjectScript; -class CGHeroInstance; -class CTown; -class CHero; -class CBuilding; -class CSpell; -class CGTownInstance; -class CGTownBuilding; -class CArtifact; -class CSpecObjInfo; -class CCastleEvent; -struct TerrainTile; -struct InfoWindow; -struct Component; -struct BankConfig; -struct UpdateHerospecialty; -struct NewArtifact; -class CGBoat; -class CArtifactSet; -class CCommanderInstance; - -class DLL_LINKAGE CQuest -{ -public: - enum Emission {MISSION_NONE = 0, MISSION_LEVEL = 1, MISSION_PRIMARY_STAT = 2, MISSION_KILL_HERO = 3, MISSION_KILL_CREATURE = 4, - MISSION_ART = 5, MISSION_ARMY = 6, MISSION_RESOURCES = 7, MISSION_HERO = 8, MISSION_PLAYER = 9, MISSION_KEYMASTER = 10}; - enum Eprogress {NOT_ACTIVE, IN_PROGRESS, COMPLETE}; - - si32 qid; //unique quest id for serialization / identification - - Emission missionType; - Eprogress progress; - si32 lastDay; //after this day (first day is 0) mission cannot be completed; if -1 - no limit - - ui32 m13489val; - std::vector m2stats; - std::vector m5arts; //artifacts id - std::vector m6creatures; //pair[cre id, cre count], CreatureSet info irrelevant - std::vector m7resources; //TODO: use resourceset? - - //following field are used only for kill creature/hero missions, the original objects became inaccessible after their removal, so we need to store info needed for messages / hover text - ui8 textOption; - CStackBasicDescriptor stackToKill; - ui8 stackDirection; - std::string heroName; //backup of hero name - si32 heroPortrait; - - std::string firstVisitText, nextVisitText, completedText; - bool isCustomFirst, isCustomNext, isCustomComplete; - - CQuest(){missionType = MISSION_NONE;}; //default constructor - virtual ~CQuest(){}; - - virtual bool checkQuest (const CGHeroInstance * h) const; //determines whether the quest is complete or not - virtual void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; - virtual void getCompletionText (MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h = nullptr) const; - virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry - virtual void completeQuest (const CGHeroInstance * h) const {}; - virtual void addReplacements(MetaString &out, const std::string &base) const; - - bool operator== (const CQuest & quest) const - { - return (quest.qid == qid); - } - - template void serialize(Handler &h, const int version) - { - h & qid & missionType & progress & lastDay & m13489val & m2stats & m5arts & m6creatures & m7resources - & textOption & stackToKill & stackDirection & heroName & heroPortrait - & firstVisitText & nextVisitText & completedText & isCustomFirst & isCustomNext & isCustomComplete; - } -}; - -class DLL_LINKAGE IObjectInterface -{ -public: - static IGameCallback *cb; - - IObjectInterface(); - virtual ~IObjectInterface(); - - virtual void onHeroVisit(const CGHeroInstance * h) const; - virtual void onHeroLeave(const CGHeroInstance * h) const; - virtual void newTurn() const; - virtual void initObj(); //synchr - virtual void setProperty(ui8 what, ui32 val);//synchr - - //Called when queries created DURING HERO VISIT are resolved - //First parameter is always hero that visited object and triggered the query - virtual void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const; - virtual void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const; - virtual void garrisonDialogClosed(const CGHeroInstance *hero) const; - virtual void heroLevelUpDone(const CGHeroInstance *hero) const; - -//unified interface, AI helpers - virtual bool wasVisited (PlayerColor player) const; - virtual bool wasVisited (const CGHeroInstance * h) const; - - static void preInit(); //called before objs receive their initObj - static void postInit();//called after objs receive their initObj - - template void serialize(Handler &h, const int version) - { - logGlobal->errorStream() << "IObjectInterface serialized, unexpected, should not happen!"; - } -}; - -class DLL_LINKAGE IBoatGenerator -{ -public: - const CGObjectInstance *o; - - IBoatGenerator(const CGObjectInstance *O); - virtual ~IBoatGenerator() {} - - virtual int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral - virtual void getOutOffsets(std::vector &offsets) const =0; //offsets to obj pos when we boat can be placed - int3 bestLocation() const; //returns location when the boat should be placed - - enum EGeneratorState {GOOD, BOAT_ALREADY_BUILT, TILE_BLOCKED, NO_WATER}; - EGeneratorState shipyardStatus() const; //0 - can buid, 1 - there is already a boat at dest tile, 2 - dest tile is blocked, 3 - no water - void getProblemText(MetaString &out, const CGHeroInstance *visitor = nullptr) const; - - template void serialize(Handler &h, const int version) - { - h & o; - } -}; - -class DLL_LINKAGE IShipyard : public IBoatGenerator -{ -public: - IShipyard(const CGObjectInstance *O); - virtual ~IShipyard() {} - - virtual void getBoatCost(std::vector &cost) const; - - static const IShipyard *castFrom(const CGObjectInstance *obj); - static IShipyard *castFrom(CGObjectInstance *obj); - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE IMarket -{ -public: - const CGObjectInstance *o; - - IMarket(const CGObjectInstance *O); - virtual ~IMarket() {} - - virtual int getMarketEfficiency() const =0; - virtual bool allowsTrade(EMarketMode::EMarketMode mode) const; - virtual int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const; //-1 if unlimited - virtual std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; - - bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode::EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units - std::vector availableModes() const; - - static const IMarket *castFrom(const CGObjectInstance *obj, bool verbose = true); - - template void serialize(Handler &h, const int version) - { - h & o; - } -}; - -class DLL_LINKAGE CGObjectInstance : public IObjectInterface -{ -public: - mutable std::string hoverName; - int3 pos; //h3m pos - Obj ID; - si32 subID; //normal subID (this one from OH3 maps ;]) - ObjectInstanceID id;//number of object in map's vector - ObjectTemplate appearance; - - PlayerColor tempOwner; - bool blockVisit; //if non-zero then blocks the tile but is visitable from neighbouring tile - - virtual ui8 getPassableness() const; //bitmap - if the bit is set the corresponding player can pass through the visitable tiles of object, even if it's blockvis; if not set - default properties from definfo are used - virtual int3 getSightCenter() const; //"center" tile from which the sight distance is calculated - virtual int getSightRadious() const; //sight distance (should be used if player-owned structure) - bool passableFor(PlayerColor color) const; - void getSightTiles(std::unordered_set &tiles) const; //returns reference to the set - PlayerColor getOwner() const; - void setOwner(PlayerColor ow); - int getWidth() const; //returns width of object graphic in tiles - int getHeight() const; //returns height of object graphic in tiles - virtual bool visitableAt(int x, int y) const; //returns true if object is visitable at location (x, y) (h3m pos) - virtual int3 getVisitableOffset() const; //returns (x,y,0) offset to first visitable tile from bottom right obj tile (0,0,0) (h3m pos) - int3 visitablePos() const; - bool blockingAt(int x, int y) const; //returns true if object is blocking location (x, y) (h3m pos) - bool coveringAt(int x, int y) const; //returns true if object covers with picture location (x, y) (h3m pos) - std::set getBlockedPos() const; //returns set of positions blocked by this object - std::set getBlockedOffsets() const; //returns set of relative positions blocked by this object - bool isVisitable() const; //returns true if object is visitable - bool operator<(const CGObjectInstance & cmp) const; //screen printing priority comparing - void hideTiles(PlayerColor ourplayer, int radius) const; - CGObjectInstance(); - virtual ~CGObjectInstance(); - //CGObjectInstance(const CGObjectInstance & right); - //CGObjectInstance& operator=(const CGObjectInstance & right); - virtual const std::string & getHoverText() const; - - ///IObjectInterface - void initObj() override; - void onHeroVisit(const CGHeroInstance * h) const override; - void setProperty(ui8 what, ui32 val) override;//synchr - - friend class CGameHandler; - - - template void serialize(Handler &h, const int version) - { - h & hoverName & pos & ID & subID & id & tempOwner & blockVisit & appearance; - //definfo is handled by map serializer - } -protected: - virtual void setPropertyDer(ui8 what, ui32 val);//synchr - - void getNameVis(std::string &hname) const; - void giveDummyBonus(ObjectInstanceID heroID, ui8 duration = Bonus::ONE_DAY) const; -}; - -/// function object which can be used to find an object with an specific sub ID -class CGObjectInstanceBySubIdFinder -{ -public: - CGObjectInstanceBySubIdFinder(CGObjectInstance * obj); - bool operator()(CGObjectInstance * obj) const; - -private: - CGObjectInstance * obj; -}; - -class CGHeroPlaceholder : public CGObjectInstance -{ -public: - //subID stores id of hero type. If it's 0xff then following field is used - ui8 power; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & power; - } -}; - -class DLL_LINKAGE CPlayersVisited: public CGObjectInstance -{ -public: - std::set players; //players that visited this object - - bool wasVisited(PlayerColor player) const; - bool wasVisited(TeamID team) const; - void setPropertyDer(ui8 what, ui32 val) override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & players; - } -}; - -class DLL_LINKAGE CArmedInstance: public CGObjectInstance, public CBonusSystemNode, public CCreatureSet -{ -public: - BattleInfo *battle; //set to the current battle, if engaged - - void randomizeArmy(int type); - virtual void updateMoraleBonusFromArmy(); - - void armyChanged() override; - - ////////////////////////////////////////////////////////////////////////// -// int valOfGlobalBonuses(CSelector selector) const; //used only for castle interface ??? - virtual CBonusSystemNode *whereShouldBeAttached(CGameState *gs); - virtual CBonusSystemNode *whatShouldBeAttached(); - ////////////////////////////////////////////////////////////////////////// - - CArmedInstance(); - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet -{ -public: - enum ECanDig - { - CAN_DIG, LACK_OF_MOVEMENT, WRONG_TERRAIN, TILE_OCCUPIED - }; - ////////////////////////////////////////////////////////////////////////// - - ui8 moveDir; //format: 123 - // 8 4 - // 765 - mutable ui8 isStanding, tacticFormationEnabled; - - ////////////////////////////////////////////////////////////////////////// - - ConstTransitivePtr type; - TExpType exp; //experience points - ui32 level; //current level of hero - std::string name; //may be custom - std::string biography; //if custom - si32 portrait; //may be custom - si32 mana; // remaining spell points - std::vector > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert); if hero has ability (-1, -1) it meansthat it should have default secondary abilities - ui32 movement; //remaining movement points - ui8 sex; - bool inTownGarrison; // if hero is in town garrison - ConstTransitivePtr visitedTown; //set if hero is visiting town or in the town garrison - ConstTransitivePtr commander; - const CGBoat *boat; //set to CGBoat when sailing - - - //std::vector artifacts; //hero's artifacts from bag - //std::map artifWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 - std::set spells; //known spells (spell IDs) - - - struct DLL_LINKAGE Patrol - { - Patrol(){patrolling=false;patrolRadious=-1;}; - bool patrolling; - ui32 patrolRadious; - template void serialize(Handler &h, const int version) - { - h & patrolling & patrolRadious; - } - } patrol; - - struct DLL_LINKAGE HeroSpecial : CBonusSystemNode - { - bool growsWithLevel; - - HeroSpecial(){growsWithLevel = false;}; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & growsWithLevel; - } - }; - - std::vector specialty; - - struct DLL_LINKAGE SecondarySkillsInfo - { - //skills are determined, initialized at map start - //FIXME remove mutable - mutable CRandomGenerator rand; - ui8 magicSchoolCounter; - ui8 wisdomCounter; - - void resetMagicSchoolCounter(); - void resetWisdomCounter(); - - template void serialize(Handler &h, const int version) - { - h & magicSchoolCounter & wisdomCounter & rand; - } - } skillsInfo; - - int3 getSightCenter() const; //"center" tile from which the sight distance is calculated - int getSightRadious() const; //sight distance (should be used if player-owned structure) - ////////////////////////////////////////////////////////////////////////// - - int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral - void getOutOffsets(std::vector &offsets) const; //offsets to obj pos when we boat can be placed - - ////////////////////////////////////////////////////////////////////////// - - bool hasSpellbook() const; - EAlignment::EAlignment getAlignment() const; - const std::string &getBiography() const; - bool needsLastStack()const; - ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling - ui32 getLowestCreatureSpeed() const; - int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' - si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day - bool canWalkOnSea() const; - int getCurrentLuck(int stack=-1, bool town=false) const; - int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored - - // ----- primary and secondary skill, experience, level handling ----- - - /// Returns true if hero has lower level than should upon his experience. - bool gainsLevel() const; - - /// Returns the next primary skill on level up. Can only be called if hero can gain a level up. - PrimarySkill::PrimarySkill nextPrimarySkill() const; - - /// Returns the next secondary skill randomly on level up. Can only be called if hero can gain a level up. - boost::optional nextSecondarySkill() const; - - /// Gets 0, 1 or 2 secondary skills which are proposed on hero level up. - std::vector getLevelUpProposedSecondarySkills() const; - - ui8 getSecSkillLevel(SecondarySkill skill) const; //0 - no skill - - /// Returns true if hero has free secondary skill slot. - bool canLearnSkill() const; - - void setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs); - void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value - void levelUp(std::vector skills); - - int maxMovePoints(bool onLand) const; - int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const; - - //int getSpellSecLevel(int spell) const; //returns level of secondary ability (fire, water, earth, air magic) known to this hero and applicable to given spell; -1 if error - static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest - double getFightingStrength() const; // takes attack / defense skill into account - double getMagicStrength() const; // takes knowledge / spell power skill into account - double getHeroStrength() const; // includes fighting and magic strength - ui64 getTotalStrength() const; // includes fighting strength and army strength - TExpType calculateXp(TExpType exp) const; //apply learning skill - ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const; //returns level on which given spell would be cast by this hero (0 - none, 1 - basic etc); optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic, - bool canCastThisSpell(const CSpell * spell) const; //determines if this hero can cast given spell; takes into account existing spell in spellbook, existing spellbook and artifact bonuses - CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const; - void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const; - ECanDig diggingStatus() const; //0 - can dig; 1 - lack of movement; 2 - - - ////////////////////////////////////////////////////////////////////////// - - void initHero(); - void initHero(HeroTypeID SUBID); - - void putArtifact(ArtifactPosition pos, CArtifactInstance *art); - void putInBackpack(CArtifactInstance *art); - void initExp(); - void initArmy(IArmyDescriptor *dst = nullptr); - //void giveArtifact (ui32 aid); - void pushPrimSkill(PrimarySkill::PrimarySkill which, int val); - ui8 maxlevelsToMagicSchool() const; - ui8 maxlevelsToWisdom() const; - void Updatespecialty(); - void recreateSecondarySkillsBonuses(); - void updateSkill(SecondarySkill which, int val); - - CGHeroInstance(); - virtual ~CGHeroInstance(); - ////////////////////////////////////////////////////////////////////////// - // - ArtBearer::ArtBearer bearerType() const override; - ////////////////////////////////////////////////////////////////////////// - - CBonusSystemNode *whereShouldBeAttached(CGameState *gs) override; - std::string nodeName() const override; - void deserializationFix(); - - void initObj() override; - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; -protected: - void setPropertyDer(ui8 what, ui32 val) override;//synchr - -private: - void levelUpAutomatically(); - -public: - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - h & exp & level & name & biography & portrait & mana & secSkills & movement - & sex & inTownGarrison & spells & patrol & moveDir & skillsInfo; - h & visitedTown & boat; - h & type & specialty & commander; - BONUS_TREE_DESERIALIZATION_FIX - //visitied town pointer will be restored by map serialization method - } -}; - -class DLL_LINKAGE CSpecObjInfo -{ -public: - virtual ~CSpecObjInfo(){}; - PlayerColor player; //owner -}; - -class DLL_LINKAGE CCreGenAsCastleInfo : public virtual CSpecObjInfo -{ -public: - bool asCastle; - ui32 identifier; - ui8 castles[2]; //allowed castles -}; - -class DLL_LINKAGE CCreGenLeveledInfo : public virtual CSpecObjInfo -{ -public: - ui8 minLevel, maxLevel; //minimal and maximal level of creature in dwelling: <0, 6> -}; - -class DLL_LINKAGE CCreGenLeveledCastleInfo : public CCreGenAsCastleInfo, public CCreGenLeveledInfo -{ -}; - -class DLL_LINKAGE CGDwelling : public CArmedInstance -{ -public: - typedef std::vector > > TCreaturesSet; - - CSpecObjInfo * info; //h3m info about dewlling - TCreaturesSet creatures; //creatures[level] -> - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this) & creatures; - } - - void initObj() override; - void onHeroVisit(const CGHeroInstance * h) const override; - void newTurn() const override; - void setProperty(ui8 what, ui32 val) override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - -private: - void heroAcceptsCreatures(const CGHeroInstance *h) const; -}; - - -class DLL_LINKAGE CGVisitableOPH : public CGObjectInstance //objects visitable only once per hero -{ -public: - std::set visitors; //ids of heroes who have visited this obj - TResources treePrice; //used only by trees of knowledge: empty, 2000 gold, 10 gems - - const std::string & getHoverText() const override; - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - bool wasVisited (const CGHeroInstance * h) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & visitors & treePrice; - } -protected: - void setPropertyDer(ui8 what, ui32 val) override;//synchr -private: - void onNAHeroVisit(const CGHeroInstance * h, bool alreadyVisited) const; - ///dialog callbacks - void treeSelected(const CGHeroInstance * h, ui32 result) const; - void schoolSelected(const CGHeroInstance * h, ui32 which) const; - void arenaSelected(const CGHeroInstance * h, int primSkill) const; -}; -class DLL_LINKAGE CGTownBuilding : public IObjectInterface -{ -///basic class for town structures handled as map objects -public: - BuildingID ID; //from buildig list - si32 id; //identifies its index on towns vector - CGTownInstance *town; - - template void serialize(Handler &h, const int version) - { - h & ID & id; - } -}; -class DLL_LINKAGE COPWBonus : public CGTownBuilding -{///used for OPW bonusing structures -public: - std::set visitors; - void setProperty(ui8 what, ui32 val) override; - void onHeroVisit (const CGHeroInstance * h) const override; - - COPWBonus (BuildingID index, CGTownInstance *TOWN); - COPWBonus (){ID = BuildingID::NONE; town = nullptr;}; - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & visitors; - } -}; - -class DLL_LINKAGE CTownBonus : public CGTownBuilding -{ -///used for one-time bonusing structures -///feel free to merge inheritance tree -public: - std::set visitors; - void setProperty(ui8 what, ui32 val) override; - void onHeroVisit (const CGHeroInstance * h) const override; - - CTownBonus (BuildingID index, CGTownInstance *TOWN); - CTownBonus (){ID = BuildingID::NONE; town = nullptr;}; - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & visitors; - } -}; - -class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode -{ -public: - CTownAndVisitingHero(); -}; - -struct DLL_LINKAGE GrowthInfo -{ - struct Entry - { - int count; - std::string description; - Entry(const std::string &format, int _count); - Entry(int subID, BuildingID building, int _count); - }; - - std::vector entries; - int totalGrowth() const; -}; - -class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket -{ -public: - enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3}; - - CTownAndVisitingHero townAndVis; - const CTown * town; - std::string name; // name of town - si32 builded; //how many buildings has been built this turn - si32 destroyed; //how many buildings has been destroyed this turn - ConstTransitivePtr garrisonHero, visitingHero; - ui32 identifier; //special identifier from h3m (only > RoE maps) - si32 alignment; - std::set forbiddenBuildings, builtBuildings; - std::vector bonusingBuildings; - std::vector possibleSpells, obligatorySpells; - std::vector > spells; //spells[level] -> vector of spells, first will be available in guild - std::list events; - std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond); - - ////////////////////////////////////////////////////////////////////////// - static std::vector merchantArtifacts; //vector of artifacts available at Artifact merchant, NULLs possible (for making empty space when artifact is bought) - static std::vector universitySkills;//skills for university of magic - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - h & static_cast(*this); - h & name & builded & destroyed & identifier; - h & garrisonHero & visitingHero; - h & alignment & forbiddenBuildings & builtBuildings & bonusValue - & possibleSpells & obligatorySpells & spells & /*strInfo & */events & bonusingBuildings; - - for (std::vector::iterator i = bonusingBuildings.begin(); i!=bonusingBuildings.end(); i++) - (*i)->town = this; - - h & town & townAndVis; - BONUS_TREE_DESERIALIZATION_FIX - - vstd::erase_if(builtBuildings, [this](BuildingID building) -> bool - { - if(!town->buildings.count(building) || !town->buildings.at(building)) - { - logGlobal->errorStream() << boost::format("#1444-like issue in CGTownInstance::serialize. From town %s at %s removing the bogus builtBuildings item %s") - % name % pos % building; - return true; - } - return false; - }); - } - ////////////////////////////////////////////////////////////////////////// - - CBonusSystemNode *whatShouldBeAttached() override; - std::string nodeName() const override; - void updateMoraleBonusFromArmy() override; - void deserializationFix(); - void recreateBuildingsBonuses(); - bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr &prop, int subtype = -1); //returns true if building is built and bonus has been added - bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype = -1); //convienence version of above - void setVisitingHero(CGHeroInstance *h); - void setGarrisonedHero(CGHeroInstance *h); - const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself - - ////////////////////////////////////////////////////////////////////////// - - ui8 getPassableness() const; //bitmap - if the bit is set the corresponding player can pass through the visitable tiles of object, even if it's blockvis; if not set - default properties from definfo are used - int3 getSightCenter() const override; //"center" tile from which the sight distance is calculated - int getSightRadious() const override; //returns sight distance - int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral - void getOutOffsets(std::vector &offsets) const; //offsets to obj pos when we boat can be placed - int getMarketEfficiency() const override; //=market count - bool allowsTrade(EMarketMode::EMarketMode mode) const; - std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; - - void updateAppearance(); - - ////////////////////////////////////////////////////////////////////////// - - bool needsLastStack() const; - CGTownInstance::EFortLevel fortLevel() const; - int hallLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol - int mageGuildLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol - int getHordeLevel(const int & HID) const; //HID - 0 or 1; returns creature level or -1 if that horde structure is not present - int creatureGrowth(const int & level) const; - GrowthInfo getGrowthInfo(int level) const; - bool hasFort() const; - bool hasCapitol() const; - //checks if building is constructed and town has same subID - bool hasBuilt(BuildingID buildingID) const; - bool hasBuilt(BuildingID buildingID, int townID) const; - TResources dailyIncome() const; //calculates daily income of this town - int spellsAtLevel(int level, bool checkGuild) const; //levels are counted from 1 (1 - 5) - bool armedGarrison() const; //true if town has creatures in garrison or garrisoned hero - int getTownLevel() const; - - void removeCapitols (PlayerColor owner) const; - void addHeroToStructureVisitors(const CGHeroInstance *h, si32 structureInstanceID) const; //hero must be visiting or garrisoned in town - - CGTownInstance(); - virtual ~CGTownInstance(); - - ///IObjectInterface overrides - void newTurn() const override; - void onHeroVisit(const CGHeroInstance * h) const override; - void onHeroLeave(const CGHeroInstance * h) const override; - void initObj() override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; -class DLL_LINKAGE CGPandoraBox : public CArmedInstance -{ -public: - std::string message; - bool hasGuardians; //helper - after battle even though we have no stacks, allows us to know that there was battle - - //gained things: - ui32 gainedExp; - si32 manaDiff; //amount of gained / lost mana - si32 moraleDiff; //morale modifier - si32 luckDiff; //luck modifier - TResources resources;//gained / lost resources - std::vector primskills;//gained / lost prim skills - std::vector abilities; //gained abilities - std::vector abilityLevels; //levels of gained abilities - std::vector artifacts; //gained artifacts - std::vector spells; //gained spells - CCreatureSet creatures; //gained creatures - - void initObj() override; - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - void heroLevelUpDone(const CGHeroInstance *hero) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & message & hasGuardians & gainedExp & manaDiff & moraleDiff & luckDiff & resources & primskills - & abilities & abilityLevels & artifacts & spells & creatures; - } -protected: - void giveContentsUpToExp(const CGHeroInstance *h) const; - void giveContentsAfterExp(const CGHeroInstance *h) const; -private: - void getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const; - void getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const; -}; - -class DLL_LINKAGE CGEvent : public CGPandoraBox //event objects -{ -public: - bool removeAfterVisit; //true if event is removed after occurring - ui8 availableFor; //players whom this event is available for - bool computerActivate; //true if computer player can activate this event - bool humanActivate; //true if human player can activate this event - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & removeAfterVisit & availableFor & computerActivate & humanActivate; - } - - void onHeroVisit(const CGHeroInstance * h) const override; -private: - void activated(const CGHeroInstance * h) const; -}; - -class DLL_LINKAGE CGCreature : public CArmedInstance //creatures on map -{ - enum Action { - FIGHT = -2, FLEE = -1, JOIN_FOR_FREE = 0 //values > 0 mean gold price - }; - -public: - ui32 identifier; //unique code for this monster (used in missions) - si8 character; //character of this set of creatures (0 - the most friendly, 4 - the most hostile) => on init changed to -4 (compliant) ... 10 value (savage) - std::string message; //message printed for attacking hero - TResources resources; // resources given to hero that has won with monsters - ArtifactID gainedArtifact; //ID of artifact gained to hero, -1 if none - bool neverFlees; //if true, the troops will never flee - bool notGrowingTeam; //if true, number of units won't grow - ui64 temppower; //used to handle fractional stack growth for tiny stacks - - bool refusedJoining; - - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; - void initObj() override; - void newTurn() const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - - struct DLL_LINKAGE formationInfo // info about merging stacks after battle back into one - { - si32 basicType; - ui32 randomFormation; //random seed used to determine number of stacks and is there's upgraded stack - template void serialize(Handler &h, const int version) - { - h & basicType & randomFormation; - } - } formation; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & identifier & character & message & resources & gainedArtifact & neverFlees & notGrowingTeam & temppower; - h & refusedJoining & formation; - } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -private: - - void fight(const CGHeroInstance *h) const; - void flee( const CGHeroInstance * h ) const; - void fleeDecision(const CGHeroInstance *h, ui32 pursue) const; - void joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const; - - int takenAction(const CGHeroInstance *h, bool allowJoin=true) const; //action on confrontation: -2 - fight, -1 - flee, >=0 - will join for given value of gold (may be 0) - -}; - - -class DLL_LINKAGE CGSignBottle : public CGObjectInstance //signs and ocean bottles -{ -public: - std::string message; - - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & message; - } -}; - -class DLL_LINKAGE IQuestObject -{ -public: - CQuest * quest; - - IQuestObject(): quest(new CQuest()){}; - virtual ~IQuestObject() {}; - virtual void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; - virtual bool checkQuest (const CGHeroInstance * h) const; - - template void serialize(Handler &h, const int version) - { - h & quest; - } -}; - -class DLL_LINKAGE CGSeerHut : public CArmedInstance, public IQuestObject //army is used when giving reward -{ -public: - enum ERewardType {NOTHING, EXPERIENCE, MANA_POINTS, MORALE_BONUS, LUCK_BONUS, RESOURCES, PRIMARY_SKILL, SECONDARY_SKILL, ARTIFACT, SPELL, CREATURE}; - ERewardType rewardType; - si32 rID; //reward ID - si32 rVal; //reward value - std::string seerName; - - CGSeerHut() : IQuestObject(){}; - void initObj() override; - const std::string & getHoverText() const override; - void newTurn() const override; - void onHeroVisit(const CGHeroInstance * h) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - virtual void init(); - int checkDirection() const; //calculates the region of map where monster is placed - void setObjToKill(); //remember creatures / heroes to kill after they are initialized - const CGHeroInstance *getHeroToKill(bool allowNull = false) const; - const CGCreature *getCreatureToKill(bool allowNull = false) const; - void getRolloverText (MetaString &text, bool onHover) const; - void getCompletionText(MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h = nullptr) const; - void finishQuest (const CGHeroInstance * h, ui32 accept) const; //common for both objects - virtual void completeQuest (const CGHeroInstance * h) const; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this) & static_cast(*this); - h & rewardType & rID & rVal & seerName; - } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; - -class DLL_LINKAGE CGQuestGuard : public CGSeerHut -{ -public: - CGQuestGuard() : CGSeerHut(){}; - void init() override; - void completeQuest (const CGHeroInstance * h) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGWitchHut : public CPlayersVisited -{ -public: - std::vector allowedAbilities; - ui32 ability; - - const std::string & getHoverText() const override; - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & allowedAbilities & ability; - } -}; - - -class DLL_LINKAGE CGScholar : public CGObjectInstance -{ -public: - enum EBonusType {PRIM_SKILL, SECONDARY_SKILL, SPELL, RANDOM = 255}; - EBonusType bonusType; - ui16 bonusID; //ID of skill/spell - -// void giveAnyBonus(const CGHeroInstance * h) const; //TODO: remove - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & bonusType & bonusID; - } -}; - -class DLL_LINKAGE CGGarrison : public CArmedInstance -{ -public: - bool removableUnits; - - ui8 getPassableness() const; - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & removableUnits; - } -}; - -class DLL_LINKAGE CGArtifact : public CArmedInstance -{ -public: - CArtifactInstance *storedArtifact; - std::string message; - - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - void pick( const CGHeroInstance * h ) const; - void initObj() override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & message & storedArtifact; - } -}; - -class DLL_LINKAGE CGResource : public CArmedInstance -{ -public: - ui32 amount; //0 if random - std::string message; - - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - void collectRes(PlayerColor player) const; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & amount & message; - } -}; - -class DLL_LINKAGE CGPickable : public CGObjectInstance //campfire, treasure chest, Flotsam, Shipwreck Survivor, Sea Chest -{ -public: - ui32 type, val1, val2; - - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & type & val1 & val2; - } -}; - -class DLL_LINKAGE CGShrine : public CPlayersVisited -{ -public: - SpellID spell; //id of spell or NONE if random - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - const std::string & getHoverText() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this);; - h & spell; - } -}; - -class DLL_LINKAGE CGMine : public CArmedInstance -{ -public: - Res::ERes producedResource; - ui32 producedQuantity; - - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - void flagMine(PlayerColor player) const; - void newTurn() const override; - void initObj() override; - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & producedResource & producedQuantity; - } - ui32 defaultResProduction(); -}; - -class DLL_LINKAGE CGVisitableOPW : public CGObjectInstance //objects visitable OPW -{ -public: - ui8 visited; //true if object has been visited this week - - bool wasVisited(PlayerColor player) const; - void onHeroVisit(const CGHeroInstance * h) const override; - virtual void newTurn() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & visited; - } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; - -class DLL_LINKAGE CGTeleport : public CGObjectInstance //teleports and subterranean gates -{ -public: - static std::map > > objs; //teleports: map[ID][subID] => vector of ids - static std::vector > gates; //subterranean gates: pairs of ids - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - static void postInit(); - static ObjectInstanceID getMatchingGate(ObjectInstanceID id); //receives id of one subterranean gate and returns id of the paired one, -1 if none - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGBonusingObject : public CGObjectInstance //objects giving bonuses to luck/morale/movement -{ -public: - bool wasVisited (const CGHeroInstance * h) const; - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; - void initObj() override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGMagicSpring : public CGVisitableOPW -{///unfortunately, this one is quite different than others - enum EVisitedEntrance - { - CLEAR = 0, LEFT = 1, RIGHT - }; -public: - EVisitedEntrance visitedTile; //only one entrance was visited - there are two - - std::vector getVisitableOffsets() const; - int3 getVisitableOffset() const override; - void setPropertyDer(ui8 what, ui32 val) override; - void newTurn() const override; - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & visitedTile & visited; - } -}; - -class DLL_LINKAGE CGMagicWell : public CGObjectInstance //objects giving bonuses to luck/morale/movement -{ -public: - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGSirens : public CGObjectInstance -{ -public: - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; - void initObj() override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGObservatory : public CGObjectInstance //Redwood observatory -{ -public: - void onHeroVisit(const CGHeroInstance * h) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - - -class DLL_LINKAGE CGKeys : public CGObjectInstance //Base class for Keymaster and guards -{ -public: - static std::map > playerKeyMap; //[players][keysowned] - //SubID 0 - lightblue, 1 - green, 2 - red, 3 - darkblue, 4 - brown, 5 - purple, 6 - white, 7 - black - - const std::string getName() const; //depending on color - bool wasMyColorVisited (PlayerColor player) const; - - const std::string & getHoverText() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; - -class DLL_LINKAGE CGKeymasterTent : public CGKeys -{ -public: - bool wasVisited (PlayerColor player) const; - void onHeroVisit(const CGHeroInstance * h) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject -{ -public: - CGBorderGuard() : IQuestObject(){}; - void initObj() override; - void onHeroVisit(const CGHeroInstance * h) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; - void getRolloverText (MetaString &text, bool onHover) const; - bool checkQuest (const CGHeroInstance * h) const; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - h & blockVisit; - } -}; - -class DLL_LINKAGE CGBorderGate : public CGBorderGuard -{ -public: - CGBorderGate() : CGBorderGuard(){}; - void onHeroVisit(const CGHeroInstance * h) const override; - - ui8 getPassableness() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); //need to serialize or object will be empty - } -}; - -class DLL_LINKAGE CGBoat : public CGObjectInstance -{ -public: - ui8 direction; - const CGHeroInstance *hero; //hero on board - - void initObj() override; - - CGBoat() - { - hero = nullptr; - direction = 4; - } - template void serialize(Handler &h, const int version) - { - h & static_cast(*this) & direction & hero; - } -}; - -class DLL_LINKAGE CGOnceVisitable : public CPlayersVisited -///wagon, corpse, lean to, warriors tomb -{ -public: - ui8 artOrRes; //0 - nothing; 1 - artifact; 2 - resource - ui32 bonusType, //id of res or artifact - bonusVal; //resource amount (or not used) - - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; - void initObj() override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this);; - h & artOrRes & bonusType & bonusVal; - } -}; - -class DLL_LINKAGE CBank : public CArmedInstance -{ - public: - int index; //banks have unusal numbering - see ZCRBANK.txt and initObj() - BankConfig *bc; - double multiplier; //for improved banks script - std::vector artifacts; //fixed and deterministic - ui32 daycounter; - - void initObj() override; - const std::string & getHoverText() const override; - void initialize() const; - void reset(ui16 var1); - void newTurn() const override; - bool wasVisited (PlayerColor player) const override; - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & index & multiplier & artifacts & daycounter & bc; - } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; -class DLL_LINKAGE CGPyramid : public CBank -{ -public: - ui16 spell; - - void initObj() override; - const std::string & getHoverText() const override; - void newTurn() const override {}; //empty, no reset - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & spell; - } -}; - -class CGShipyard : public CGObjectInstance, public IShipyard -{ -public: - void getOutOffsets(std::vector &offsets) const; //offsets to obj pos when we boat can be placed - CGShipyard(); - void onHeroVisit(const CGHeroInstance * h) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGMagi : public CGObjectInstance -{ -public: - static std::map > eyelist; //[subID][id], supports multiple sets as in H5 - - void initObj() override; - void onHeroVisit(const CGHeroInstance * h) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - - - -class DLL_LINKAGE CCartographer : public CPlayersVisited -{ -///behaviour varies depending on surface and floor -public: - void onHeroVisit(const CGHeroInstance * h) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGDenOfthieves : public CGObjectInstance -{ - void onHeroVisit(const CGHeroInstance * h) const override; -}; - -class DLL_LINKAGE CGObelisk : public CPlayersVisited -{ -public: - static ui8 obeliskCount; //how many obelisks are on map - static std::map visited; //map: team_id => how many obelisks has been visited - - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - const std::string & getHoverText() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; - -class DLL_LINKAGE CGLighthouse : public CGObjectInstance -{ -public: - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - const std::string & getHoverText() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } - void giveBonusTo( PlayerColor player ) const; -}; - -class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket -{ -public: - CGMarket(); - ///IObjectIntercae - void onHeroVisit(const CGHeroInstance * h) const override; //open trading window - - ///IMarket - int getMarketEfficiency() const override; - bool allowsTrade(EMarketMode::EMarketMode mode) const override; - int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const override; //-1 if unlimited - std::vector availableItemsIds(EMarketMode::EMarketMode mode) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGBlackMarket : public CGMarket -{ -public: - std::vector artifacts; //available artifacts - - void newTurn() const override; //reset artifacts for black market every month - std::vector availableItemsIds(EMarketMode::EMarketMode mode) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & artifacts; - } -}; - -class DLL_LINKAGE CGUniversity : public CGMarket -{ -public: - std::vector skills; //available skills - - std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; - void initObj() override;//set skills for trade - void onHeroVisit(const CGHeroInstance * h) const override; //open window - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & skills; - } -}; - -struct BankConfig -{ - BankConfig() {level = chance = upgradeChance = combatValue = value = rewardDifficulty = easiest = 0; }; - ui8 level; //1 - 4, how hard the battle will be - ui8 chance; //chance for this level being chosen - ui8 upgradeChance; //chance for creatures to be in upgraded versions - std::vector< std::pair > guards; //creature ID, amount - ui32 combatValue; //how hard are guards of this level - Res::ResourceSet resources; //resources given in case of victory - std::vector< std::pair > creatures; //creatures granted in case of victory (creature ID, amount) - std::vector artifacts; //number of artifacts given in case of victory [0] -> treasure, [1] -> minor [2] -> major [3] -> relic - ui32 value; //overall value of given things - ui32 rewardDifficulty; //proportion of reward value to difficulty of guards; how profitable is this creature Bank config - ui16 easiest; //?!? - - template void serialize(Handler &h, const int version) - { - h & level & chance & upgradeChance & guards & combatValue & resources & creatures & artifacts & value & rewardDifficulty & easiest; - } -}; - -class DLL_LINKAGE CObjectHandler -{ -public: - std::map cregens; //type 17. dwelling subid -> creature ID - std::map > > banksInfo; //[index][preset] - std::map creBanksNames; //[crebank index] -> name of this creature bank - std::vector resVals; //default values of resources in gold - - CObjectHandler(); - ~CObjectHandler(); - - int bankObjToIndex (const CGObjectInstance * obj); - - template void serialize(Handler &h, const int version) - { - h & cregens & banksInfo & creBanksNames & resVals; - } -}; diff --git a/lib/CRandomGenerator.h b/lib/CRandomGenerator.h index eefb91082..a77aa9240 100644 --- a/lib/CRandomGenerator.h +++ b/lib/CRandomGenerator.h @@ -110,4 +110,14 @@ namespace RandomGeneratorUtil assert(!container.empty()); return std::next(container.begin(), rand.nextInt(container.size() - 1)); } + + template + void randomShuffle(std::vector& container, CRandomGenerator & rand) + { + int n = (container.end() - container.begin()); + for (int i = n-1; i>0; --i) + { + std::swap (container.begin()[i],container.begin()[rand.nextInt(i+1)]); + } + } } diff --git a/client/CSoundBase.h b/lib/CSoundBase.h similarity index 100% rename from client/CSoundBase.h rename to lib/CSoundBase.h diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index e5fb7662b..a31d0a65e 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -11,8 +11,8 @@ #include "CArtHandler.h" #include "CSpellHandler.h" #include "filesystem/Filesystem.h" -#include "CDefObjInfoHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectClassesHandler.h" +#include "mapObjects/CObjectHandler.h" /* * CTownHandler.cpp, part of VCMI engine @@ -272,24 +272,12 @@ void CTownHandler::loadBuildingRequirements(CTown &town, CBuilding & building, c { if (source.isNull()) return; - if (source.Vector()[0].getType() == JsonNode::DATA_FLOAT) - { - // MODS COMPATIBILITY - CBuilding::TRequired::OperatorAll required; - for(const JsonNode &building : source.Vector()) - required.expressions.push_back(BuildingID(building.Float())); - - building.requirements = CBuilding::TRequired(required); - } - else - { - BuildingRequirementsHelper hlp; - hlp.building = &building; - hlp.faction = town.faction; - hlp.json = source; - requirementsToLoad.push_back(hlp); - } + BuildingRequirementsHelper hlp; + hlp.building = &building; + hlp.faction = town.faction; + hlp.json = source; + requirementsToLoad.push_back(hlp); } void CTownHandler::loadBuilding(CTown &town, const std::string & stringID, const JsonNode & source) @@ -308,56 +296,49 @@ void CTownHandler::loadBuilding(CTown &town, const std::string & stringID, const ret->resources = TResources(source["cost"]); ret->produce = TResources(source["produce"]); - //for compatibility with older town mods + //MODS COMPATIBILITY FOR 0.96 if(!ret->produce.nonZero()) { - if (ret->bid == BuildingID::VILLAGE_HALL) ret->produce[Res::GOLD] = 500; - - if (ret->bid == BuildingID::TOWN_HALL) ret->produce[Res::GOLD] = 1000; - - if (ret->bid == BuildingID::CITY_HALL) ret->produce[Res::GOLD] = 2000; - - if (ret->bid == BuildingID::CAPITOL) ret->produce[Res::GOLD] = 4000; - - if (ret->bid == BuildingID::GRAIL) ret->produce[Res::GOLD] = 5000; - // - if (ret->bid == BuildingID::RESOURCE_SILO) - { - if ((ret->town->primaryRes != Res::WOOD) && (ret->town->primaryRes != Res::ORE) && (ret->town->primaryRes != Res::GOLD)) - ret->produce[ret->town->primaryRes] = 1; - else - { - if (ret->town->primaryRes == Res::GOLD) ret->produce[ret->town->primaryRes] = 500; - else + switch (ret->bid) { + break; case BuildingID::VILLAGE_HALL: ret->produce[Res::GOLD] = 500; + break; case BuildingID::TOWN_HALL : ret->produce[Res::GOLD] = 1000; + break; case BuildingID::CITY_HALL : ret->produce[Res::GOLD] = 2000; + break; case BuildingID::CAPITOL : ret->produce[Res::GOLD] = 4000; + break; case BuildingID::GRAIL : ret->produce[Res::GOLD] = 5000; + break; case BuildingID::RESOURCE_SILO : { - ret->produce[Res::WOOD] = 1; - ret->produce[Res::ORE] = 1; + switch (ret->town->primaryRes) + { + case Res::GOLD: + ret->produce[ret->town->primaryRes] = 500; + break; + case Res::WOOD_AND_ORE: + ret->produce[Res::WOOD] = 1; + ret->produce[Res::ORE] = 1; + break; + default: + ret->produce[ret->town->primaryRes] = 1; + break; + } } } - } } loadBuildingRequirements(town, *ret, source["requires"]); if (!source["upgrades"].isNull()) { - //MODS COMPATIBILITY - if (source["upgrades"].getType() == JsonNode::DATA_FLOAT) - ret->upgrade = BuildingID(source["upgrades"].Float()); - else + // building id and upgrades can't be the same + if(stringID == source["upgrades"].String()) { - // building id and upgrades can't be the same - if(stringID == source["upgrades"].String()) - { - throw std::runtime_error(boost::str(boost::format("Building with ID '%s' of town '%s' can't be an upgrade of the same building.") % - stringID % town.faction->name)); - } - - VLC->modh->identifiers.requestIdentifier("building." + town.faction->identifier, source["upgrades"], [=](si32 identifier) - { - ret->upgrade = BuildingID(identifier); - }); + throw std::runtime_error(boost::str(boost::format("Building with ID '%s' of town '%s' can't be an upgrade of the same building.") % + stringID % town.faction->name)); } + + VLC->modh->identifiers.requestIdentifier("building." + town.faction->identifier, source["upgrades"], [=](si32 identifier) + { + ret->upgrade = BuildingID(identifier); + }); } else ret->upgrade = BuildingID::NONE; @@ -381,7 +362,6 @@ void CTownHandler::loadStructure(CTown &town, const std::string & stringID, cons { auto ret = new CStructure; - //Note: MODS COMPATIBILITY CODE ret->building = nullptr; ret->buildable = nullptr; @@ -399,17 +379,10 @@ void CTownHandler::loadStructure(CTown &town, const std::string & stringID, cons } else { - if (source["builds"].getType() == JsonNode::DATA_FLOAT) + VLC->modh->identifiers.requestIdentifier("building." + town.faction->identifier, source["builds"], [=, &town](si32 identifier) mutable { - ret->buildable = town.buildings[BuildingID(source["builds"].Float())]; - } - else - { - VLC->modh->identifiers.requestIdentifier("building." + town.faction->identifier, source["builds"], [=, &town](si32 identifier) mutable - { - ret->buildable = town.buildings[BuildingID(identifier)]; - }); - } + ret->buildable = town.buildings[BuildingID(identifier)]; + }); } ret->identifier = stringID; @@ -457,16 +430,10 @@ void CTownHandler::loadTownHall(CTown &town, const JsonNode & source) auto & dst = dstBox[k]; auto & src = srcBox[k]; - //MODS COMPATIBILITY - if (src.getType() == JsonNode::DATA_FLOAT) - dst = BuildingID(src.Float()); - else + VLC->modh->identifiers.requestIdentifier("building." + town.faction->identifier, src, [&](si32 identifier) { - VLC->modh->identifiers.requestIdentifier("building." + town.faction->identifier, src, [&](si32 identifier) - { - dst = BuildingID(identifier); - }); - } + dst = BuildingID(identifier); + }); } } } @@ -554,10 +521,6 @@ void CTownHandler::loadClientData(CTown &town, const JsonNode & source) info.tavernVideo = "TAVERN.BIK"; //end of legacy assignment - info.advMapVillage = source["adventureMap"]["village"].String(); - info.advMapCastle = source["adventureMap"]["castle"].String(); - info.advMapCapitol = source["adventureMap"]["capitol"].String(); - loadTownHall(town, source["hallSlots"]); loadStructures(town, source["structures"]); loadSiegeScreen(town, source["siege"]); @@ -677,20 +640,6 @@ CFaction * CTownHandler::loadFromJson(const JsonNode &source, std::string identi faction->name = source["name"].String(); faction->identifier = identifier; - //FIXME: MODS COMPATIBILITY - if (!source["commander"].isNull()) - { - VLC->modh->identifiers.requestIdentifier ("creature", source["commander"], - [=](si32 commanderID) - { - for (auto ptr : VLC->heroh->classes.heroClasses) - { - if (ptr->commander == nullptr && ptr->faction == faction->index) - ptr->commander = VLC->creh->creatures[commanderID]; - } - }); - } - faction->creatureBg120 = source["creatureBackground"]["120px"].String(); faction->creatureBg130 = source["creatureBackground"]["130px"].String(); @@ -723,6 +672,8 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod auto object = loadFromJson(data, name); object->index = factions.size(); + factions.push_back(object); + if (object->town) { auto & info = object->town->clientInfo; @@ -730,9 +681,16 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod info.icons[0][1] = 8 + object->index * 4 + 1; info.icons[1][0] = 8 + object->index * 4 + 2; info.icons[1][1] = 8 + object->index * 4 + 3; - } - factions.push_back(object); + VLC->modh->identifiers.requestIdentifier(scope, "object", "town", [=](si32 index) + { + // register town once objects are loaded + JsonNode config = data["town"]["mapObject"]; + config["faction"].String() = object->identifier; + config["faction"].meta = scope; + VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index); + }); + } VLC->modh->identifiers.registerObject(scope, "faction", name, object->index); } @@ -741,6 +699,9 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod { auto object = loadFromJson(data, name); object->index = index; + assert(factions[index] == nullptr); // ensure that this id was not loaded before + factions[index] = object; + if (object->town) { auto & info = object->town->clientInfo; @@ -748,10 +709,25 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod info.icons[0][1] = (GameConstants::F_NUMBER + object->index) * 2 + 1; info.icons[1][0] = object->index * 2 + 0; info.icons[1][1] = object->index * 2 + 1; - } - assert(factions[index] == nullptr); // ensure that this id was not loaded before - factions[index] = object; + VLC->modh->identifiers.requestIdentifier(scope, "object", "town", [=](si32 index) + { + // register town once objects are loaded + JsonNode config = data["town"]["mapObject"]; + config["faction"].String() = object->identifier; + config["faction"].meta = scope; + VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index); + + // MODS COMPATIBILITY FOR 0.96 + auto & advMap = data["town"]["adventureMap"]; + if (!advMap["fort"].isNull()) + { + JsonNode config; + config["appearance"] = advMap["fort"]; + VLC->objtypeh->getHandlerFor(index, object->index)->addTemplate(config); + } + }); + } VLC->modh->identifiers.registerObject(scope, "faction", name, object->index); } @@ -759,38 +735,6 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod void CTownHandler::afterLoadFinalization() { initializeRequirements(); - ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::TOWN, 0).front(); - for (CFaction * fact : factions) - { - if (fact->town) - { - base.animationFile = fact->town->clientInfo.advMapCastle; - base.subid = fact->index; - - // replace existing (if any) and add new template. - // Necessary for objects added via mods that don't have any templates in H3 - VLC->dobjinfo->eraseAll(Obj::TOWN, fact->index); - VLC->dobjinfo->registerTemplate(base); - - assert(fact->town->dwellings.size() == fact->town->dwellingNames.size()); - for (size_t i=0; itown->dwellings.size(); i++) - { - ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::CREATURE_GENERATOR1, 0).front(); - - //both unupgraded and upgraded get same dwelling - for (auto cre : fact->town->creatures[i]) - { - base.subid = 80 + cre; - base.animationFile = fact->town->dwellings[i]; - if (VLC->objh->cregens.count(cre) == 0) - { - VLC->dobjinfo->registerTemplate(base); - VLC->objh->cregens[80 + cre] = cre; //map of dwelling -> creature id - } - } - } - } - } } void CTownHandler::initializeRequirements() @@ -824,4 +768,4 @@ std::set CTownHandler::getAllowedFactions() const allowedFactions.insert(i); return allowedFactions; -} \ No newline at end of file +} diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 6f9c0a266..5fcebcbd9 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -190,10 +190,6 @@ public: /// NOTE: index in vector is meaningless. Vector used instead of list for a bit faster access std::vector > structures; - std::string advMapVillage; - std::string advMapCastle; - std::string advMapCapitol; - std::string siegePrefix; std::vector siegePositions; CreatureID siegeShooter; // shooter creature ID @@ -201,7 +197,7 @@ public: template void serialize(Handler &h, const int version) { h & icons & iconSmall & iconLarge & tavernVideo & musicTheme & townBackground & guildBackground & guildWindow & buildingsIcons & hallBackground; - h & advMapVillage & advMapCastle & advMapCapitol & hallSlots & structures; + h & hallSlots & structures; h & siegePrefix & siegePositions & siegeShooter; } } clientInfo; diff --git a/lib/Connection.h b/lib/Connection.h index 9044b20eb..cfb51deb8 100644 --- a/lib/Connection.h +++ b/lib/Connection.h @@ -14,7 +14,6 @@ #include //XXX this is in namespace std if you want w/o use typeinfo.h? #include -#include #include #include #include @@ -24,7 +23,7 @@ #include "ConstTransitivePtr.h" #include "CCreatureSet.h" //for CStackInstance -#include "CObjectHandler.h" //for CArmedInstance +#include "mapObjects/CGHeroInstance.h" #include "mapping/CCampaignHandler.h" //for CCampaignState #include "rmg/CMapGenerator.h" // for CMapGenOptions diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index 82bb431a1..beca83dfc 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -13,7 +13,7 @@ #include "StdInc.h" #include "VCMI_Lib.h" -#include "CDefObjInfoHandler.h" +#include "mapObjects/CObjectClassesHandler.h" #include "CArtHandler.h" #include "CCreatureHandler.h" #include "CSpellHandler.h" diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 48c74c3b0..98bcf2c4a 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -14,9 +14,10 @@ #include "CHeroHandler.h" // for CHeroHandler #include "CSpellHandler.h" // for CSpell #include "NetPacks.h" -#include "CBonusTypeHandler.h" // for CBonusTypeHandler +#include "CBonusTypeHandler.h" #include "Connection.h" // for SAVEGAME_MAGIC +#include "mapObjects/CObjectClassesHandler.h" void CPrivilagedInfoCallback::getFreeTiles (std::vector &tiles) const { diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index 03f9ce6d7..e5f6bf58a 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -737,7 +737,7 @@ void JsonUtils::mergeCopy(JsonNode & dest, JsonNode source) void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base) { - JsonNode inheritedNode(base); + JsonNode inheritedNode(base); merge(inheritedNode,descendant); descendant.swap(inheritedNode); } diff --git a/lib/JsonNode.h b/lib/JsonNode.h index c1cc6fb5d..dfe9aba0b 100644 --- a/lib/JsonNode.h +++ b/lib/JsonNode.h @@ -108,19 +108,14 @@ public: template void serialize(Handler &h, const int version) { h & meta; - // simple saving - save json in its string interpretation - if (h.saving) - { - std::ostringstream stream; - stream << *this; - std::string str = stream.str(); - h & str; - } - else - { - std::string str; - h & str; - JsonNode(str.c_str(), str.size()).swap(*this); + h & type; + switch (type) { + break; case DATA_NULL: + break; case DATA_BOOL: h & data.Bool; + break; case DATA_FLOAT: h & data.Float; + break; case DATA_STRING: h & data.String; + break; case DATA_VECTOR: h & data.Vector; + break; case DATA_STRUCT: h & data.Struct; } } }; diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 66a3dc9cf..ea6cc2c62 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1,11 +1,10 @@ #pragma once -#include - #include "NetPacksBase.h" #include "BattleAction.h" #include "HeroBonus.h" +#include "mapObjects/CGHeroInstance.h" #include "CCreatureSet.h" #include "mapping/CMapInfo.h" #include "StartInfo.h" @@ -1022,11 +1021,10 @@ namespace ObjProperty BONUS_VALUE_FIRST, BONUS_VALUE_SECOND, //used in Rampart for special building that generates resources (storing resource type and quantity) //creature-bank specific - BANK_DAYCOUNTER, BANK_CLEAR_ARTIFACTS, BANK_ADD_ARTIFACT, BANK_MULTIPLIER, BANK_CONFIG_PRESET, - BANK_CLEAR_CONFIG, BANK_INIT_ARMY, BANK_RESET, + BANK_DAYCOUNTER, BANK_RESET, BANK_CLEAR, - //magic spring - LEFT_VISITED, RIGHT_VISITED, LEFTRIGHT_CLEAR + //object with reward + REWARD_RESET, REWARD_SELECT }; } @@ -1036,7 +1034,7 @@ struct SetObjectProperty : public CPackForClient//1001 void applyCl(CClient *cl); ObjectInstanceID id; - ui8 what; //1 - owner; 2 - blockvis; 3 - first stack count; 4 - visitors; 5 - visited; 6 - ID (if 34 then also def is replaced) + ui8 what; // see ObjProperty enum ui32 val; SetObjectProperty(){type = 1001;}; SetObjectProperty(ObjectInstanceID ID, ui8 What, ui32 Val):id(ID),what(What),val(Val){type = 1001;}; @@ -1053,14 +1051,44 @@ struct SetHoverName : public CPackForClient//1002 ObjectInstanceID id; MetaString name; - SetHoverName(){type = 1002;}; - SetHoverName(ObjectInstanceID ID, MetaString& Name):id(ID),name(Name){type = 1002;}; + SetHoverName(){type = 1002;} + SetHoverName(ObjectInstanceID ID, MetaString& Name):id(ID),name(Name){type = 1002;} template void serialize(Handler &h, const int version) { h & id & name; } }; + +struct ChangeObjectVisitors : public CPackForClient // 1003 +{ + enum VisitMode + { + VISITOR_ADD, // mark hero as one that have visited this object + VISITOR_REMOVE, // unmark visitor, reversed to ADD + VISITOR_CLEAR // clear all visitors from this object (object reset) + }; + ui32 mode; // uses VisitMode enum + ObjectInstanceID object; + ObjectInstanceID hero; // note: hero owner will be also marked as "visited" this object + + DLL_LINKAGE void applyGs(CGameState *gs); + + ChangeObjectVisitors() + { type = 1003; } + + ChangeObjectVisitors(ui32 mode, ObjectInstanceID object, ObjectInstanceID heroID = ObjectInstanceID(-1)): + mode(mode), + object(object), + hero(heroID) + { type = 1003; } + + template void serialize(Handler &h, const int version) + { + h & object & hero & mode; + } +}; + struct HeroLevelUp : public Query//2000 { void applyCl(CClient *cl); @@ -1499,7 +1527,7 @@ struct ObstaclesRemoved : public CPackForClient //3014 } }; -struct CatapultAttack : public CPackForClient //3015 +struct DLL_LINKAGE CatapultAttack : public CPackForClient //3015 { struct AttackInfo { @@ -1515,8 +1543,7 @@ struct CatapultAttack : public CPackForClient //3015 } }; - DLL_LINKAGE CatapultAttack(); - DLL_LINKAGE ~CatapultAttack(); + CatapultAttack() {type = 3015;} DLL_LINKAGE void applyGs(CGameState *gs); void applyCl(CClient *cl); diff --git a/lib/NetPacksBase.h b/lib/NetPacksBase.h index aca2fd62d..e365d6b76 100644 --- a/lib/NetPacksBase.h +++ b/lib/NetPacksBase.h @@ -25,7 +25,7 @@ struct ArtSlotInfo; #include "GameConstants.h" -struct CPack +struct DLL_LINKAGE CPack { ui16 type; @@ -37,7 +37,7 @@ struct CPack logNetwork->errorStream() << "CPack serialized... this should not happen!"; } void applyGs(CGameState *gs) { } - virtual std::string toString() const { return boost::str(boost::format("{CPack: type '%d'}") % type); } + DLL_LINKAGE virtual std::string toString() const { return boost::str(boost::format("{CPack: type '%d'}") % type); } }; std::ostream & operator<<(std::ostream & out, const CPack * pack); @@ -197,4 +197,4 @@ struct ArtifactLocation { h & artHolder & slot; } -}; \ No newline at end of file +}; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index acd1e9e62..5e86e6b49 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -2,10 +2,10 @@ #include "NetPacks.h" #include "CGeneralTextHandler.h" -#include "CDefObjInfoHandler.h" +#include "mapObjects/CObjectClassesHandler.h" #include "CArtHandler.h" #include "CHeroHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "CModHandler.h" #include "VCMI_Lib.h" #include "mapping/CMap.h" @@ -124,6 +124,8 @@ DLL_LINKAGE void HeroVisitCastle::applyGs( CGameState *gs ) CGHeroInstance *h = gs->getHero(hid); CGTownInstance *t = gs->getTown(tid); + assert(h); + assert(t); if(start()) t->setVisitingHero(h); else @@ -227,15 +229,14 @@ DLL_LINKAGE void GiveBonus::applyGs( CGameState *gs ) && gs->map->objects[bonus.sid]->ID == Obj::EVENT) //it's morale/luck bonus from an event without description { descr = VLC->generaltexth->arraytxt[bonus.val > 0 ? 110 : 109]; //+/-%d Temporary until next battle" - - // Some of(?) versions of H3 use %s here instead of %d. Try to replace both of them - boost::replace_first(descr,"%d",boost::lexical_cast(std::abs(bonus.val))); - boost::replace_first(descr,"%s",boost::lexical_cast(std::abs(bonus.val))); } else { bdescr.toString(descr); } + // Some of(?) versions of H3 use %s here instead of %d. Try to replace both of them + boost::replace_first(descr,"%d",boost::lexical_cast(std::abs(bonus.val))); + boost::replace_first(descr,"%s",boost::lexical_cast(std::abs(bonus.val))); } DLL_LINKAGE void ChangeObjPos::applyGs( CGameState *gs ) @@ -251,6 +252,23 @@ DLL_LINKAGE void ChangeObjPos::applyGs( CGameState *gs ) gs->map->addBlockVisTiles(obj); } +DLL_LINKAGE void ChangeObjectVisitors::applyGs( CGameState *gs ) +{ + switch (mode) { + case VISITOR_ADD: + gs->getHero(hero)->visitedObjects.insert(object); + gs->getPlayer(gs->getHero(hero)->tempOwner)->visitedObjects.insert(object); + break; + case VISITOR_CLEAR: + for (CGHeroInstance * hero : gs->map->allHeroes) + hero->visitedObjects.erase(object); // remove visit info from all heroes, including those that are not present on map + break; + case VISITOR_REMOVE: + gs->getHero(hero)->visitedObjects.erase(object); + break; + } +} + DLL_LINKAGE void PlayerEndsGame::applyGs( CGameState *gs ) { PlayerState *p = gs->getPlayer(player); @@ -547,7 +565,7 @@ DLL_LINKAGE void GiveHero::applyGs( CGameState *gs ) //bonus system h->detachFrom(&gs->globalEffects); h->attachTo(gs->getPlayer(player)); - h->appearance = VLC->dobjinfo->pickCandidates(Obj::HERO, h->type->heroClass->id).front(); + h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->type->heroClass->id)->getTemplates().front(); gs->map->removeBlockVisTiles(h,true); h->setOwner(player); @@ -588,9 +606,9 @@ DLL_LINKAGE void NewObject::applyGs( CGameState *gs ) o->subID = subID; o->pos = pos; const TerrainTile &t = gs->map->getTile(pos); - o->appearance = VLC->dobjinfo->pickCandidates(o->ID, o->subID, t.terType).front(); + o->appearance = VLC->objtypeh->getHandlerFor(o->ID, o->subID)->getTemplates(t.terType).front(); id = o->id = ObjectInstanceID(gs->map->objects.size()); - o->hoverName = VLC->generaltexth->names[ID]; + o->hoverName = VLC->objtypeh->getObjectName(ID); gs->map->objects.push_back(o); gs->map->addBlockVisTiles(o); @@ -1477,16 +1495,6 @@ DLL_LINKAGE void ObstaclesRemoved::applyGs( CGameState *gs ) } } -DLL_LINKAGE CatapultAttack::CatapultAttack() -{ - type = 3015; -} - -DLL_LINKAGE CatapultAttack::~CatapultAttack() -{ - -} - DLL_LINKAGE void CatapultAttack::applyGs( CGameState *gs ) { if(gs->curB && gs->curB->siege != CGTownInstance::NONE) //if there is a battle and it's a siege diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 8c6af64b3..620663058 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -14,9 +14,9 @@ #include "CArtHandler.h" #include "CBonusTypeHandler.h" #include "CCreatureHandler.h" -#include "CDefObjInfoHandler.h" +#include "mapObjects/CObjectClassesHandler.h" #include "CHeroHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "CTownHandler.h" #include "CBuildingHandler.h" #include "CSpellHandler.h" @@ -45,11 +45,11 @@ DLL_LINKAGE void preinitDLL(CConsoleHandler *Console) DLL_LINKAGE void loadDLLClasses() { - try + //try { VLC->init(); } - HANDLE_EXCEPTION; + //HANDLE_EXCEPTION; } const IBonusTypeHandler * LibClasses::getBth() const @@ -109,7 +109,7 @@ void LibClasses::init() createHandler(objh, "Object", pomtime); - createHandler(dobjinfo, "Def information", pomtime); + createHandler(objtypeh, "Object types information", pomtime); createHandler(spellh, "Spell", pomtime); @@ -135,7 +135,7 @@ void LibClasses::clear() delete creh; delete townh; delete objh; - delete dobjinfo; + delete objtypeh; delete spellh; delete modh; delete bth; @@ -152,7 +152,7 @@ void LibClasses::makeNull() creh = nullptr; townh = nullptr; objh = nullptr; - dobjinfo = nullptr; + objtypeh = nullptr; spellh = nullptr; modh = nullptr; bth = nullptr; diff --git a/lib/VCMI_Lib.h b/lib/VCMI_Lib.h index b1b52b9bf..3cd5e60f6 100644 --- a/lib/VCMI_Lib.h +++ b/lib/VCMI_Lib.h @@ -16,7 +16,7 @@ class CCreatureHandler; class CSpellHandler; class CBuildingHandler; class CObjectHandler; -class CDefObjInfoHandler; +class CObjectClassesHandler; class CTownHandler; class CGeneralTextHandler; class CModHandler; @@ -42,7 +42,7 @@ public: CCreatureHandler * creh; CSpellHandler * spellh; CObjectHandler * objh; - CDefObjInfoHandler * dobjinfo; + CObjectClassesHandler * objtypeh; CTownHandler * townh; CGeneralTextHandler * generaltexth; CModHandler * modh; @@ -60,7 +60,7 @@ public: template void serialize(Handler &h, const int version) { - h & heroh & arth & creh & townh & objh & dobjinfo & spellh & modh & IS_AI_ENABLED; + h & heroh & arth & creh & townh & objh & objtypeh & spellh & modh & IS_AI_ENABLED; h & bth; if(!h.saving) { diff --git a/lib/VCMI_lib.cbp b/lib/VCMI_lib.cbp index c1312720b..843390d99 100644 --- a/lib/VCMI_lib.cbp +++ b/lib/VCMI_lib.cbp @@ -132,8 +132,6 @@ - - @@ -146,8 +144,6 @@ - - @@ -226,6 +222,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -246,6 +273,7 @@ + diff --git a/lib/mapObjects/CArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp new file mode 100644 index 000000000..050c10f38 --- /dev/null +++ b/lib/mapObjects/CArmedInstance.cpp @@ -0,0 +1,128 @@ +/* + * CArmedInstance.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CArmedInstance.h" + +#include "../CTownHandler.h" +#include "../CCreatureHandler.h" +#include "../CGeneralTextHandler.h" +#include "../CGameState.h" + +using namespace boost::assign; + +void CArmedInstance::randomizeArmy(int type) +{ + for (auto & elem : stacks) + { + int & randID = elem.second->idRand; + if(randID >= 0) + { + int level = randID / 2; + bool upgrade = randID % 2; + elem.second->setType(VLC->townh->factions[type]->town->creatures[level][upgrade]); + + randID = -1; + } + assert(elem.second->valid(false)); + assert(elem.second->armyObj == this); + } + return; +} + +CArmedInstance::CArmedInstance() +{ + battle = nullptr; +} + +void CArmedInstance::updateMoraleBonusFromArmy() +{ + if(!validTypes(false)) //object not randomized, don't bother + return; + + Bonus *b = getBonusList().getFirst(Selector::sourceType(Bonus::ARMY).And(Selector::type(Bonus::MORALE))); + if(!b) + { + b = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1); + addNewBonus(b); + } + + //number of alignments and presence of undead + std::set factions; + bool hasUndead = false; + + for(auto slot : Slots()) + { + const CStackInstance * inst = slot.second; + const CCreature * creature = VLC->creh->creatures[inst->getCreatureID()]; + + factions.insert(creature->faction); + // Check for undead flag instead of faction (undead mummies are neutral) + hasUndead |= inst->hasBonusOfType(Bonus::UNDEAD); + } + + size_t factionsInArmy = factions.size(); //town garrison seems to take both sets into account + + // Take Angelic Alliance troop-mixing freedom of non-evil units into account. + if (hasBonusOfType(Bonus::NONEVIL_ALIGNMENT_MIX)) + { + size_t mixableFactions = 0; + + for(TFaction f : factions) + { + if (VLC->townh->factions[f]->alignment != EAlignment::EVIL) + mixableFactions++; + } + if (mixableFactions > 0) + factionsInArmy -= mixableFactions - 1; + } + + if(factionsInArmy == 1) + { + b->val = +1; + b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1 + } + else if (!factions.empty()) // no bonus from empty garrison + { + b->val = 2 - factionsInArmy; + b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d + } + boost::algorithm::trim(b->description); + + //-1 modifier for any Undead unit in army + const ui8 UNDEAD_MODIFIER_ID = -2; + Bonus *undeadModifier = getBonusList().getFirst(Selector::source(Bonus::ARMY, UNDEAD_MODIFIER_ID)); + if(hasUndead) + { + if(!undeadModifier) + addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116])); + } + else if(undeadModifier) + removeBonus(undeadModifier); + +} + +void CArmedInstance::armyChanged() +{ + updateMoraleBonusFromArmy(); +} + +CBonusSystemNode * CArmedInstance::whereShouldBeAttached(CGameState *gs) +{ + if(tempOwner < PlayerColor::PLAYER_LIMIT) + return gs->getPlayer(tempOwner); + else + return &gs->globalEffects; +} + +CBonusSystemNode * CArmedInstance::whatShouldBeAttached() +{ + return this; +} diff --git a/lib/mapObjects/CArmedInstance.h b/lib/mapObjects/CArmedInstance.h new file mode 100644 index 000000000..f85b8a07e --- /dev/null +++ b/lib/mapObjects/CArmedInstance.h @@ -0,0 +1,40 @@ +#pragma once + +#include "CObjectHandler.h" +#include "../CCreatureSet.h" + +/* + * CArmedInstance.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 + * + */ + +class DLL_LINKAGE CArmedInstance: public CGObjectInstance, public CBonusSystemNode, public CCreatureSet +{ +public: + BattleInfo *battle; //set to the current battle, if engaged + + void randomizeArmy(int type); + virtual void updateMoraleBonusFromArmy(); + + void armyChanged() override; + + ////////////////////////////////////////////////////////////////////////// +// int valOfGlobalBonuses(CSelector selector) const; //used only for castle interface ??? + virtual CBonusSystemNode *whereShouldBeAttached(CGameState *gs); + virtual CBonusSystemNode *whatShouldBeAttached(); + ////////////////////////////////////////////////////////////////////////// + + CArmedInstance(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + h & static_cast(*this); + } +}; diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp new file mode 100644 index 000000000..8f4f52582 --- /dev/null +++ b/lib/mapObjects/CBank.cpp @@ -0,0 +1,329 @@ +/* + * CBank.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CBank.h" + +#include "../NetPacks.h" +#include "../CGeneralTextHandler.h" +#include "../CSoundBase.h" +#include "CommonConstructors.h" +#include "../CSpellHandler.h" + +using namespace boost::assign; + +///helpers +static std::string & visitedTxt(const bool visited) +{ + int id = visited ? 352 : 353; + return VLC->generaltexth->allTexts[id]; +} + +CBank::CBank() +{ +} + +CBank::~CBank() +{ +} + +void CBank::initObj() +{ + daycounter = 0; + resetDuration = 0; + VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, cb->gameState()->getRandomGenerator()); +} + +const std::string & CBank::getHoverText() const +{ + bool visited = (bc == nullptr); + hoverName = visitedTxt(visited); // FIXME: USE BANK_SPECIFIC NAMES + return hoverName; +} + +void CBank::setConfig(const BankConfig & config) +{ + bc.reset(new BankConfig(config)); + clear(); // remove all stacks, if any + + for (auto & stack : config.guards) + setCreature (SlotID(stacksCount()), stack.type->idNumber, stack.count); +} + +void CBank::setPropertyDer (ui8 what, ui32 val) +{ + switch (what) + { + case ObjProperty::BANK_DAYCOUNTER: //daycounter + daycounter+=val; + break; + case ObjProperty::BANK_RESET: + initObj(); + daycounter = 1; //yes, 1 since "today" daycounter won't be incremented + break; + case ObjProperty::BANK_CLEAR: + bc.reset(); + break; + } +} + +void CBank::newTurn() const +{ + if (bc == nullptr) + { + if (resetDuration != 0) + { + if (daycounter >= resetDuration) + cb->setObjProperty (id, ObjProperty::BANK_RESET, 0); //daycounter 0 + else + cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 1); //daycounter++ + } + } +} + +bool CBank::wasVisited (PlayerColor player) const +{ + return !bc; //FIXME: player A should not know about visit done by player B +} + +void CBank::onHeroVisit (const CGHeroInstance * h) const +{ + if (bc) + { + int banktext = 0; + ui16 soundID = soundBase::ROGUE; + switch (ID) + { + case Obj::CREATURE_BANK: + banktext = 32; + break; + case Obj::DERELICT_SHIP: + banktext = 41; + break; + case Obj::DRAGON_UTOPIA: + banktext = 47; + break; + case Obj::CRYPT: + banktext = 119; + break; + case Obj::SHIPWRECK: + banktext = 122; + break; + case Obj::PYRAMID: + soundID = soundBase::MYSTERY; + banktext = 105; + break; + } + BlockingDialog bd (true, false); + bd.player = h->getOwner(); + bd.soundID = soundID; + bd.text.addTxt(MetaString::ADVOB_TXT, banktext); + //if (ID == Obj::CREATURE_BANK) + // bd.text.addReplacement(VLC->objh->creBanksNames[index]); // FIXME: USE BANK SPECIFIC NAMES + cb->showBlockingDialog (&bd); + } + else + { + InfoWindow iw; + iw.soundID = soundBase::GRAVEYARD; + iw.player = h->getOwner(); + if (ID == Obj::PYRAMID) // You come upon the pyramid ... pyramid is completely empty. + { + iw.text << VLC->generaltexth->advobtxt[107]; + iw.components.push_back (Component (Component::LUCK, 0 , -2, 0)); + GiveBonus gb; + gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,-2,id.getNum(),VLC->generaltexth->arraytxt[70]); + gb.id = h->id.getNum(); + cb->giveHeroBonus(&gb); + } + else + { + iw.text << VLC->generaltexth->advobtxt[33];// This was X, now is completely empty + //iw.text.addReplacement(VLC->objh->creBanksNames[index]); // FIXME: USE BANK SPECIFIC NAMES + } + cb->showInfoDialog(&iw); + } +} + +void CBank::doVisit(const CGHeroInstance * hero) const +{ + int textID = -1; + InfoWindow iw; + iw.player = hero->getOwner(); + MetaString loot; + + switch (ID) + { + case Obj::CREATURE_BANK: + case Obj::DRAGON_UTOPIA: + textID = 34; + break; + case Obj::DERELICT_SHIP: + if (!bc) + textID = 43; + else + { + GiveBonus gbonus; + gbonus.id = hero->id.getNum(); + gbonus.bonus.duration = Bonus::ONE_BATTLE; + gbonus.bonus.source = Bonus::OBJECT; + gbonus.bonus.sid = ID; + gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[101]; + gbonus.bonus.type = Bonus::MORALE; + gbonus.bonus.val = -1; + cb->giveHeroBonus(&gbonus); + textID = 42; + iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); + } + break; + case Obj::CRYPT: + if (bc) + textID = 121; + else + { + iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); + GiveBonus gbonus; + gbonus.id = hero->id.getNum(); + gbonus.bonus.duration = Bonus::ONE_BATTLE; + gbonus.bonus.source = Bonus::OBJECT; + gbonus.bonus.sid = ID; + gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[ID]; + gbonus.bonus.type = Bonus::MORALE; + gbonus.bonus.val = -1; + cb->giveHeroBonus(&gbonus); + textID = 120; + iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); + } + break; + case Obj::SHIPWRECK: + if (bc) + textID = 124; + else + textID = 123; + break; + case Obj::PYRAMID: + textID = 106; + } + + //grant resources + if (bc) + { + for (int it = 0; it < bc->resources.size(); it++) + { + if (bc->resources[it] != 0) + { + iw.components.push_back (Component (Component::RESOURCE, it, bc->resources[it], 0)); + loot << "%d %s"; + loot.addReplacement(iw.components.back().val); + loot.addReplacement(MetaString::RES_NAMES, iw.components.back().subtype); + cb->giveResource (hero->getOwner(), static_cast(it), bc->resources[it]); + } + } + //grant artifacts + for (auto & elem : bc->artifacts) + { + iw.components.push_back (Component (Component::ARTIFACT, elem, 0, 0)); + loot << "%s"; + loot.addReplacement(MetaString::ART_NAMES, elem); + cb->giveHeroNewArtifact (hero, VLC->arth->artifacts[elem], ArtifactPosition::FIRST_AVAILABLE); + } + //display loot + if (!iw.components.empty()) + { + iw.text.addTxt (MetaString::ADVOB_TXT, textID); + if (textID == 34) + { + const CCreature * strongest = boost::range::max_element(bc->guards, [](const CStackBasicDescriptor & a, const CStackBasicDescriptor & b) + { + return a.type->fightValue < b.type->fightValue; + })->type; + + iw.text.addReplacement(MetaString::CRE_PL_NAMES, strongest->idNumber); + iw.text.addReplacement(loot.buildList()); + } + cb->showInfoDialog(&iw); + } + + if (!bc->spells.empty()) + { + std::set spells; + + bool noWisdom = false; + for (SpellID spell : bc->spells) + { + iw.text.addTxt (MetaString::SPELL_NAME, spell); + if (VLC->spellh->objects[spell]->level <= hero->getSecSkillLevel(SecondarySkill::WISDOM) + 2) + { + spells.insert(spell); + iw.components.push_back(Component (Component::SPELL, spell, 0, 0)); + } + else + noWisdom = true; + } + + if (!hero->getArt(ArtifactPosition::SPELLBOOK)) + iw.text.addTxt (MetaString::ADVOB_TXT, 109); //no spellbook + else if (noWisdom) + iw.text.addTxt (MetaString::ADVOB_TXT, 108); //no expert Wisdom + if (spells.empty()) + cb->changeSpells (hero, true, spells); + } + loot.clear(); + iw.components.clear(); + iw.text.clear(); + + //grant creatures + CCreatureSet ourArmy; + for (auto slot : bc->creatures) + { + ourArmy.addToSlot(ourArmy.getSlotFor(slot.type->idNumber), slot.type->idNumber, slot.count); + } + + for (auto & elem : ourArmy.Slots()) + { + iw.components.push_back(Component(*elem.second)); + loot << "%s"; + loot.addReplacement(*elem.second); + } + + if (ourArmy.Slots().size()) + { + if (ourArmy.Slots().size() == 1 && ourArmy.Slots().begin()->second->count == 1) + iw.text.addTxt (MetaString::ADVOB_TXT, 185); + else + iw.text.addTxt (MetaString::ADVOB_TXT, 186); + + iw.text.addReplacement(loot.buildList()); + iw.text.addReplacement(hero->name); + cb->showInfoDialog(&iw); + cb->giveCreatures(this, hero, ourArmy, false); + } + cb->setObjProperty (id, ObjProperty::BANK_CLEAR, 0); //bc = nullptr + } +} + +void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if (result.winner == 0) + { + doVisit(hero); + } +} + +void CBank::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if (answer) + { + if (bc) // not looted bank + cb->startBattleI(hero, this, true); + else + doVisit(hero); + } +} diff --git a/lib/mapObjects/CBank.h b/lib/mapObjects/CBank.h new file mode 100644 index 000000000..c8f8471aa --- /dev/null +++ b/lib/mapObjects/CBank.h @@ -0,0 +1,49 @@ +#pragma once + +#include "CObjectHandler.h" +#include "CArmedInstance.h" + +/* + * CBank.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 + * + */ + +class BankConfig; +class CBankInstanceConstructor; + +class DLL_LINKAGE CBank : public CArmedInstance +{ + std::unique_ptr bc; + ui32 daycounter; + ui32 resetDuration; + + void setPropertyDer(ui8 what, ui32 val) override; + void doVisit(const CGHeroInstance * hero) const; + +public: + CBank(); + ~CBank(); + + void setConfig(const BankConfig & bc); + + void initObj() override; + const std::string & getHoverText() const override; + void newTurn() const override; + bool wasVisited (PlayerColor player) const override; + void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & daycounter & bc & resetDuration; + } + + friend class CBankInstanceConstructor; +}; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp new file mode 100644 index 000000000..61e5da8c5 --- /dev/null +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -0,0 +1,1372 @@ +/* + * CGHeroInstance.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CGHeroInstance.h" + +#include "../NetPacks.h" +#include "../CGeneralTextHandler.h" +#include "../CHeroHandler.h" +#include "../CSoundBase.h" +#include "../CSpellHandler.h" +#include "CObjectClassesHandler.h" + + +using namespace boost::assign; + +///helpers +static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) +{ + InfoWindow iw; + iw.soundID = soundID; + iw.player = playerID; + iw.text.addTxt(MetaString::ADVOB_TXT,txtID); + IObjectInterface::cb->sendAndApply(&iw); +} + +static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID) +{ + const PlayerColor playerID = h->getOwner(); + showInfoDialog(playerID,txtID,soundID); +} + +static int lowestSpeed(const CGHeroInstance * chi) +{ + if(!chi->Slots().size()) + { + logGlobal->errorStream() << "Error! Hero " << chi->id.getNum() << " ("<name<<") has no army!"; + return 20; + } + auto i = chi->Slots().begin(); + //TODO? should speed modifiers (eg from artifacts) affect hero movement? + int ret = (i++)->second->valOfBonuses(Bonus::STACKS_SPEED); + for (;i!=chi->Slots().end();i++) + { + ret = std::min(ret, i->second->valOfBonuses(Bonus::STACKS_SPEED)); + } + return ret; +} + +ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from) const +{ + //base move cost + unsigned ret = 100; + + //if there is road both on dest and src tiles - use road movement cost + if(dest.roadType != ERoadType::NO_ROAD && from.roadType != ERoadType::NO_ROAD) + { + int road = std::min(dest.roadType,from.roadType); //used road ID + switch(road) + { + case ERoadType::DIRT_ROAD: + ret = 75; + break; + case ERoadType::GRAVEL_ROAD: + ret = 65; + break; + case ERoadType::COBBLESTONE_ROAD: + ret = 50; + break; + default: + logGlobal->errorStream() << "Unknown road type: " << road << "... Something wrong!"; + break; + } + } + else + { + //FIXME: in H3 presence of Nomad in army will remove terrain penalty for sand. Bonus not implemented in VCMI + + // NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army. + // This is clearly bug in H3 however intended behaviour is not clear. + // Current VCMI behaviour will ignore neutrals in calculations so army in VCMI + // will always have best penalty without any influence from player-defined stacks order + + bool nativeArmy = true; + for(auto stack : stacks) + { + int nativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain; + + if (nativeTerrain != -1 && nativeTerrain != from.terType) + { + nativeArmy = false; + break; + } + } + if (!nativeArmy) + ret = VLC->heroh->terrCosts[from.terType]; + } + return ret; +} + +int3 CGHeroInstance::convertPosition(int3 src, bool toh3m) //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest +{ + if (toh3m) + { + src.x+=1; + return src; + } + else + { + src.x-=1; + return src; + } +} +int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' +{ + if (h3m) + { + return pos; + } + else + { + return convertPosition(pos,false); + } +} + +bool CGHeroInstance::canWalkOnSea() const +{ + return hasBonusOfType(Bonus::FLYING_MOVEMENT) || hasBonusOfType(Bonus::WATER_WALKING); +} + +ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const +{ + for(auto & elem : secSkills) + if(elem.first == skill) + return elem.second; + return 0; +} + +void CGHeroInstance::setSecSkillLevel(SecondarySkill which, int val, bool abs) +{ + if(getSecSkillLevel(which) == 0) + { + secSkills.push_back(std::pair(which, val)); + updateSkill(which, val); + } + else + { + for (auto & elem : secSkills) + { + if(elem.first == which) + { + if(abs) + elem.second = val; + else + elem.second += val; + + if(elem.second > 3) //workaround to avoid crashes when same sec skill is given more than once + { + logGlobal->warnStream() << "Warning: Skill " << which << " increased over limit! Decreasing to Expert."; + elem.second = 3; + } + updateSkill(which, elem.second); //when we know final value + } + } + } +} + +bool CGHeroInstance::canLearnSkill() const +{ + return secSkills.size() < GameConstants::SKILL_PER_HERO; +} + +int CGHeroInstance::maxMovePoints(bool onLand) const +{ + int base; + + if(onLand) + { + // used function is f(x) = 66.6x + 1300, rounded to second digit, where x is lowest speed in army + static const int baseSpeed = 1300; // base speed from creature with 0 speed + + int armySpeed = lowestSpeed(this) * 20 / 3; + + base = armySpeed * 10 + baseSpeed; // separate *10 is intentional to receive same rounding as in h3 + vstd::abetween(base, 1500, 2000); // base speed is limited by these values + } + else + { + base = 1500; //on water base movement is always 1500 (speed of army doesn't matter) + } + + const Bonus::BonusType bt = onLand ? Bonus::LAND_MOVEMENT : Bonus::SEA_MOVEMENT; + const int bonus = valOfBonuses(Bonus::MOVEMENT) + valOfBonuses(bt); + + const int subtype = onLand ? SecondarySkill::LOGISTICS : SecondarySkill::NAVIGATION; + const double modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0; + + return int(base* (1+modifier)) + bonus; +} + +CGHeroInstance::CGHeroInstance() + : IBoatGenerator(this) +{ + setNodeType(HERO); + ID = Obj::HERO; + tacticFormationEnabled = inTownGarrison = false; + mana = movement = portrait = level = -1; + isStanding = true; + moveDir = 4; + exp = 0xffffffff; + visitedTown = nullptr; + type = nullptr; + boat = nullptr; + commander = nullptr; + sex = 0xff; + secSkills.push_back(std::make_pair(SecondarySkill::DEFAULT, -1)); +} + +void CGHeroInstance::initHero(HeroTypeID SUBID) +{ + subID = SUBID.getNum(); + initHero(); +} + +void CGHeroInstance::setType(si32 ID, si32 subID) +{ + assert(ID == Obj::HERO); // just in case + type = VLC->heroh->heroes[subID]; + portrait = type->imageIndex; + CGObjectInstance::setType(ID, type->heroClass->id); + randomizeArmy(type->heroClass->faction); +} + +void CGHeroInstance::initHero() +{ + assert(validTypes(true)); + if(!type) + type = VLC->heroh->heroes[subID]; + + if (ID == Obj::HERO) + appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->id)->getTemplates().front(); + + if(!vstd::contains(spells, SpellID::PRESET)) //hero starts with a spell + { + for(auto spellID : type->spells) + spells.insert(spellID); + } + else //remove placeholder + spells -= SpellID::PRESET; + + if(!getArt(ArtifactPosition::MACH4) && !getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook) //no catapult means we haven't read pre-existent set -> use default rules for spellbook + putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0)); + + if(!getArt(ArtifactPosition::MACH4)) + putArtifact(ArtifactPosition::MACH4, CArtifactInstance::createNewArtifactInstance(3)); //everyone has a catapult + + if(portrait < 0 || portrait == 255) + portrait = type->imageIndex; + if(!hasBonus(Selector::sourceType(Bonus::HERO_BASE_SKILL))) + { + for(int g=0; g(g), type->heroClass->primarySkillInitial[g]); + } + } + if(secSkills.size() == 1 && secSkills[0] == std::pair(SecondarySkill::DEFAULT, -1)) //set secondary skills to default + secSkills = type->secSkillsInit; + if (!name.length()) + name = type->name; + + if (sex == 0xFF)//sex is default + sex = type->sex; + + setFormation(false); + if (!stacksCount()) //standard army//initial army + { + initArmy(); + } + assert(validTypes()); + + level = 1; + if(exp == 0xffffffff) + { + initExp(); + } + else + { + levelUpAutomatically(); + } + + if (VLC->modh->modules.COMMANDERS && !commander) + { + commander = new CCommanderInstance(type->heroClass->commander->idNumber); + commander->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders + commander->giveStackExp (exp); //after our exp is set + } + + if (mana < 0) + mana = manaLimit(); +} + +void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= nullptr*/) +{ + if(!dst) + dst = this; + + int howManyStacks = 0; //how many stacks will hero receives <1 - 3> + int pom = cb->gameState()->getRandomGenerator().nextInt(99); + int warMachinesGiven = 0; + + if(pom < 9) + howManyStacks = 1; + else if(pom < 79) + howManyStacks = 2; + else + howManyStacks = 3; + + vstd::amin(howManyStacks, type->initialArmy.size()); + + for(int stackNo=0; stackNo < howManyStacks; stackNo++) + { + auto & stack = type->initialArmy[stackNo]; + + int count = cb->gameState()->getRandomGenerator().nextInt(stack.minAmount, stack.maxAmount); + + if(stack.creature >= CreatureID::CATAPULT && + stack.creature <= CreatureID::ARROW_TOWERS) //war machine + { + warMachinesGiven++; + if(dst != this) + continue; + + int slot = -1; + ArtifactID aid = ArtifactID::NONE; + switch (stack.creature) + { + case CreatureID::CATAPULT: + slot = ArtifactPosition::MACH4; + aid = ArtifactID::CATAPULT; + break; + default: + aid = CArtHandler::creatureToMachineID(stack.creature); + slot = 9 + aid; + break; + } + auto convSlot = ArtifactPosition(slot); + if(!getArt(convSlot)) + putArtifact(convSlot, CArtifactInstance::createNewArtifactInstance(aid)); + else + logGlobal->warnStream() << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid; + } + else + dst->setCreature(SlotID(stackNo-warMachinesGiven), stack.creature, count); + } +} + +CGHeroInstance::~CGHeroInstance() +{ + commander.dellNull(); +} + +bool CGHeroInstance::needsLastStack() const +{ + return true; +} + +void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const +{ + if(h == this) return; //exclude potential self-visiting + + if (ID == Obj::HERO) + { + if( cb->gameState()->getPlayerRelations(tempOwner, h->tempOwner)) //our or ally hero + { + //exchange + cb->heroExchange(h->id, id); + } + else //battle + { + if(visitedTown) //we're in town + visitedTown->onHeroVisit(h); //town will handle attacking + else + cb->startBattleI(h, this); + } + } + else if(ID == Obj::PRISON) + { + int txt_id; + + if (cb->getHeroCount(h->tempOwner, false) < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)//GameConstants::MAX_HEROES_PER_PLAYER) //free hero slot + { + cb->changeObjPos(id,pos+int3(1,0,0),0); + //update hero parameters + SetMovePoints smp; + smp.hid = id; + smp.val = maxMovePoints (true); //TODO: hota prison on water? + cb->setMovePoints (&smp); + cb->setManaPoints (id, manaLimit()); + + cb->setObjProperty(id, ObjProperty::ID, Obj::HERO); //set ID to 34 + cb->giveHero(id,h->tempOwner); //recreates def and adds hero to player + + txt_id = 102; + } + else //already 8 wandering heroes + { + txt_id = 103; + } + + showInfoDialog(h,txt_id,soundBase::ROGUE); + } +} + +const std::string & CGHeroInstance::getHoverText() const +{ + if(ID != Obj::PRISON) + { + hoverName = VLC->generaltexth->allTexts[15]; + boost::algorithm::replace_first(hoverName,"%s",name); + boost::algorithm::replace_first(hoverName,"%s", type->heroClass->name); + return hoverName; + } + else + hoverName = VLC->objtypeh->getObjectName(ID); + + return hoverName; +} + +const std::string & CGHeroInstance::getBiography() const +{ + if (biography.length()) + return biography; + return type->biography; +} + +ui8 CGHeroInstance::maxlevelsToMagicSchool() const +{ + return type->heroClass->isMagicHero() ? 3 : 4; +} +ui8 CGHeroInstance::maxlevelsToWisdom() const +{ + return type->heroClass->isMagicHero() ? 3 : 6; +} + +void CGHeroInstance::SecondarySkillsInfo::resetMagicSchoolCounter() +{ + magicSchoolCounter = 1; +} +void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter() +{ + wisdomCounter = 1; +} + +void CGHeroInstance::initObj() +{ + blockVisit = true; + auto hs = new HeroSpecial(); + hs->setNodeType(CBonusSystemNode::SPECIALTY); + attachTo(hs); //do we ever need to detach it? + + if(!type) + initHero(); //TODO: set up everything for prison before specialties are configured + + skillsInfo.rand.setSeed(cb->gameState()->getRandomGenerator().nextInt()); + skillsInfo.resetMagicSchoolCounter(); + skillsInfo.resetWisdomCounter(); + + auto customApp = VLC->objtypeh->getHandlerFor(ID, type->heroClass->id)->getOverride(cb->gameState()->getTile(visitablePos())->terType, this); + if (customApp) + appearance = customApp.get(); + + for(const auto &spec : type->spec) //TODO: unfity with bonus system + { + auto bonus = new Bonus(); + bonus->val = spec.val; + bonus->sid = id.getNum(); //from the hero, specialty has no unique id + bonus->duration = Bonus::PERMANENT; + bonus->source = Bonus::HERO_SPECIAL; + switch (spec.type) + { + case 1:// creature specialty + { + hs->growsWithLevel = true; + + const CCreature &specCreature = *VLC->creh->creatures[spec.additionalinfo]; //creature in which we have specialty + + //int creLevel = specCreature.level; + //if(!creLevel) + //{ + // if(spec.additionalinfo == 146) + // creLevel = 5; //treat ballista as 5-level + // else + // { + // logGlobal->warnStream() << "Warning: unknown level of " << specCreature.namePl; + // continue; + // } + //} + + //bonus->additionalInfo = spec.additionalinfo; //creature id, should not be used again - this works only with limiter + bonus->limiter.reset(new CCreatureTypeLimiter (specCreature, true)); //with upgrades + bonus->type = Bonus::PRIMARY_SKILL; + bonus->valType = Bonus::ADDITIVE_VALUE; + + bonus->subtype = PrimarySkill::ATTACK; + hs->addNewBonus(bonus); + + bonus = new Bonus(*bonus); + bonus->subtype = PrimarySkill::DEFENSE; + hs->addNewBonus(bonus); + //values will be calculated later + + bonus = new Bonus(*bonus); + bonus->type = Bonus::STACKS_SPEED; + bonus->val = 1; //+1 speed + hs->addNewBonus(bonus); + } + break; + case 2://secondary skill + hs->growsWithLevel = true; + bonus->type = Bonus::SPECIAL_SECONDARY_SKILL; //needs to be recalculated with level, based on this value + bonus->valType = Bonus::BASE_NUMBER; // to receive nonzero value + bonus->subtype = spec.subtype; //skill id + bonus->val = spec.val; //value per level, in percent + hs->addNewBonus(bonus); + bonus = new Bonus(*bonus); + + switch (spec.additionalinfo) + { + case 0: //normal + bonus->valType = Bonus::PERCENT_TO_BASE; + break; + case 1: //when it's navigation or there's no 'base' at all + bonus->valType = Bonus::PERCENT_TO_ALL; + break; + } + bonus->type = Bonus::SECONDARY_SKILL_PREMY; //value will be calculated later + hs->addNewBonus(bonus); + break; + case 3://spell damage bonus, level dependent but calculated elsewhere + bonus->type = Bonus::SPECIAL_SPELL_LEV; + bonus->subtype = spec.subtype; + hs->addNewBonus(bonus); + break; + case 4://creature stat boost + switch (spec.subtype) + { + case 1://attack + bonus->type = Bonus::PRIMARY_SKILL; + bonus->subtype = PrimarySkill::ATTACK; + break; + case 2://defense + bonus->type = Bonus::PRIMARY_SKILL; + bonus->subtype = PrimarySkill::DEFENSE; + break; + case 3: + bonus->type = Bonus::CREATURE_DAMAGE; + bonus->subtype = 0; //both min and max + break; + case 4://hp + bonus->type = Bonus::STACK_HEALTH; + break; + case 5: + bonus->type = Bonus::STACKS_SPEED; + break; + default: + continue; + } + bonus->additionalInfo = spec.additionalinfo; //creature id + bonus->valType = Bonus::ADDITIVE_VALUE; + bonus->limiter.reset(new CCreatureTypeLimiter (*VLC->creh->creatures[spec.additionalinfo], true)); + hs->addNewBonus(bonus); + break; + case 5://spell damage bonus in percent + bonus->type = Bonus::SPECIFIC_SPELL_DAMAGE; + bonus->valType = Bonus::BASE_NUMBER; // current spell system is screwed + bonus->subtype = spec.subtype; //spell id + hs->addNewBonus(bonus); + break; + case 6://damage bonus for bless (Adela) + bonus->type = Bonus::SPECIAL_BLESS_DAMAGE; + bonus->subtype = spec.subtype; //spell id if you ever wanted to use it otherwise + bonus->additionalInfo = spec.additionalinfo; //damage factor + hs->addNewBonus(bonus); + break; + case 7://maxed mastery for spell + bonus->type = Bonus::MAXED_SPELL; + bonus->subtype = spec.subtype; //spell i + hs->addNewBonus(bonus); + break; + case 8://peculiar spells - enchantments + bonus->type = Bonus::SPECIAL_PECULIAR_ENCHANT; + bonus->subtype = spec.subtype; //spell id + bonus->additionalInfo = spec.additionalinfo;//0, 1 for Coronius + hs->addNewBonus(bonus); + break; + case 9://upgrade creatures + { + const auto &creatures = VLC->creh->creatures; + bonus->type = Bonus::SPECIAL_UPGRADE; + bonus->subtype = spec.subtype; //base id + bonus->additionalInfo = spec.additionalinfo; //target id + hs->addNewBonus(bonus); + bonus = new Bonus(*bonus); + + for(auto cre_id : creatures[spec.subtype]->upgrades) + { + bonus->subtype = cre_id; //propagate for regular upgrades of base creature + hs->addNewBonus(bonus); + bonus = new Bonus(*bonus); + } + vstd::clear_pointer(bonus); + break; + } + case 10://resource generation + bonus->type = Bonus::GENERATE_RESOURCE; + bonus->subtype = spec.subtype; + hs->addNewBonus(bonus); + break; + case 11://starting skill with mastery (Adrienne) + setSecSkillLevel(SecondarySkill(spec.val), spec.additionalinfo, true); + break; + case 12://army speed + bonus->type = Bonus::STACKS_SPEED; + hs->addNewBonus(bonus); + break; + case 13://Dragon bonuses (Mutare) + bonus->type = Bonus::PRIMARY_SKILL; + bonus->valType = Bonus::ADDITIVE_VALUE; + switch (spec.subtype) + { + case 1: + bonus->subtype = PrimarySkill::ATTACK; + break; + case 2: + bonus->subtype = PrimarySkill::DEFENSE; + break; + } + bonus->limiter.reset(new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE)); + hs->addNewBonus(bonus); + break; + default: + logGlobal->warnStream() << "Unexpected hero specialty " << type; + } + } + specialty.push_back(hs); //will it work? + + for (auto hs2 : type->specialty) //copy active (probably growing) bonuses from hero prootype to hero object + { + auto hs = new HeroSpecial(); + attachTo(hs); //do we ever need to detach it? + + hs->setNodeType(CBonusSystemNode::SPECIALTY); + for (auto bonus : hs2.bonuses) + { + hs->addNewBonus (bonus); + } + hs->growsWithLevel = hs2.growsWithLevel; + + specialty.push_back(hs); //will it work? + } + + //initialize bonuses + recreateSecondarySkillsBonuses(); + Updatespecialty(); + + mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one + type->name = name; +} +void CGHeroInstance::Updatespecialty() //TODO: calculate special value of bonuses on-the-fly? +{ + for (auto hs : specialty) + { + if (hs->growsWithLevel) + { + //const auto &creatures = VLC->creh->creatures; + + for(Bonus * b : hs->getBonusList()) + { + switch (b->type) + { + case Bonus::SECONDARY_SKILL_PREMY: + b->val = (hs->valOfBonuses(Bonus::SPECIAL_SECONDARY_SKILL, b->subtype) * level); + break; //use only hero skills as bonuses to avoid feedback loop + case Bonus::PRIMARY_SKILL: //for creatures, that is + { + const CCreature * cre = nullptr; + int creLevel = 0; + if (auto creatureLimiter = std::dynamic_pointer_cast(b->limiter)) //TODO: more general eveluation of bonuses? + { + cre = creatureLimiter->creature; + creLevel = cre->level; + if (!creLevel) + { + creLevel = 5; //treat ballista as tier 5 + } + } + else //no creature found, can't calculate value + { + logGlobal->warnStream() << "Primary skill specialty growth supported only with creature type limiters"; + break; + } + + double primSkillModifier = (int)(level / creLevel) / 20.0; + int param; + switch (b->subtype) + { + case PrimarySkill::ATTACK: + param = cre->Attack(); + break; + case PrimarySkill::DEFENSE: + param = cre->Defense(); + break; + default: + continue; + } + b->val = ceil(param * (1 + primSkillModifier)) - param; //yep, overcomplicated but matches original + break; + } + } + } + } + } +} + +void CGHeroInstance::recreateSecondarySkillsBonuses() +{ + auto secondarySkillsBonuses = getBonuses(Selector::sourceType(Bonus::SECONDARY_SKILL)); + for(auto bonus : *secondarySkillsBonuses) + removeBonus(bonus); + + for(auto skill_info : secSkills) + updateSkill(SecondarySkill(skill_info.first), skill_info.second); +} + +void CGHeroInstance::updateSkill(SecondarySkill which, int val) +{ + if(which == SecondarySkill::LEADERSHIP || which == SecondarySkill::LUCK) + { //luck-> VLC->generaltexth->arraytxt[73+luckSkill]; VLC->generaltexth->arraytxt[104+moraleSkill] + bool luck = which == SecondarySkill::LUCK; + Bonus::BonusType type[] = {Bonus::MORALE, Bonus::LUCK}; + + Bonus *b = getBonusLocalFirst(Selector::type(type[luck]).And(Selector::sourceType(Bonus::SECONDARY_SKILL))); + if(!b) + { + b = new Bonus(Bonus::PERMANENT, type[luck], Bonus::SECONDARY_SKILL, +val, which, which, Bonus::BASE_NUMBER); + addNewBonus(b); + } + else + b->val = +val; + } + else if(which == SecondarySkill::DIPLOMACY) //surrender discount: 20% per level + { + + if(Bonus *b = getBonusLocalFirst(Selector::type(Bonus::SURRENDER_DISCOUNT).And(Selector::sourceType(Bonus::SECONDARY_SKILL)))) + b->val = +val; + else + addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::SURRENDER_DISCOUNT, Bonus::SECONDARY_SKILL, val * 20, which)); + } + + int skillVal = 0; + switch (which) + { + case SecondarySkill::ARCHERY: + switch (val) + { + case 1: + skillVal = 10; break; + case 2: + skillVal = 25; break; + case 3: + skillVal = 50; break; + } + break; + case SecondarySkill::LOGISTICS: + skillVal = 10 * val; break; + case SecondarySkill::NAVIGATION: + skillVal = 50 * val; break; + case SecondarySkill::MYSTICISM: + skillVal = val; break; + case SecondarySkill::EAGLE_EYE: + skillVal = 30 + 10 * val; break; + case SecondarySkill::NECROMANCY: + skillVal = 10 * val; break; + case SecondarySkill::LEARNING: + skillVal = 5 * val; break; + case SecondarySkill::OFFENCE: + skillVal = 10 * val; break; + case SecondarySkill::ARMORER: + skillVal = 5 * val; break; + case SecondarySkill::INTELLIGENCE: + skillVal = 25 << (val-1); break; + case SecondarySkill::SORCERY: + skillVal = 5 * val; break; + case SecondarySkill::RESISTANCE: + skillVal = 5 << (val-1); break; + case SecondarySkill::FIRST_AID: + skillVal = 25 + 25*val; break; + case SecondarySkill::ESTATES: + skillVal = 125 << (val-1); break; + } + + + Bonus::ValueType skillValType = skillVal ? Bonus::BASE_NUMBER : Bonus::INDEPENDENT_MIN; + if(Bonus * b = getBonusList().getFirst(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, which) + .And(Selector::sourceType(Bonus::SECONDARY_SKILL)))) //only local hero bonus + { + b->val = skillVal; + b->valType = skillValType; + } + else + { + auto bonus = new Bonus(Bonus::PERMANENT, Bonus::SECONDARY_SKILL_PREMY, Bonus::SECONDARY_SKILL, skillVal, id.getNum(), which, skillValType); + bonus->source = Bonus::SECONDARY_SKILL; + addNewBonus(bonus); + } + +} +void CGHeroInstance::setPropertyDer( ui8 what, ui32 val ) +{ + if(what == ObjProperty::PRIMARY_STACK_COUNT) + setStackCount(SlotID(0), val); +} + +double CGHeroInstance::getFightingStrength() const +{ + return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE))); +} + +double CGHeroInstance::getMagicStrength() const +{ + return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::KNOWLEDGE)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::SPELL_POWER))); +} + +double CGHeroInstance::getHeroStrength() const +{ + return sqrt(pow(getFightingStrength(), 2.0) * pow(getMagicStrength(), 2.0)); +} + +ui64 CGHeroInstance::getTotalStrength() const +{ + double ret = getFightingStrength() * getArmyStrength(); + return (ui64) ret; +} + +TExpType CGHeroInstance::calculateXp(TExpType exp) const +{ + return exp * (100 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::LEARNING))/100.0; +} + +ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool) const +{ + si16 skill = -1; //skill level + +#define TRY_SCHOOL(schoolName, schoolMechanicsId, schoolOutId) \ + if(spell-> schoolName) \ + { \ + int thisSchool = std::max(getSecSkillLevel( \ + SecondarySkill(14 + (schoolMechanicsId))), \ + valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << (schoolMechanicsId))); \ + if(thisSchool > skill) \ + { \ + skill = thisSchool; \ + if(outSelectedSchool) \ + *outSelectedSchool = schoolOutId; \ + } \ + } + TRY_SCHOOL(fire, 0, 1) + TRY_SCHOOL(air, 1, 0) + TRY_SCHOOL(water, 2, 2) + TRY_SCHOOL(earth, 3, 3) +#undef TRY_SCHOOL + + + + vstd::amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus + vstd::amax(skill, valOfBonuses(Bonus::SPELL, spell->id.toEnum())); //given by artifact or other effect + if (hasBonusOfType(Bonus::MAXED_SPELL, spell->id))//hero specialty (Daremyth, Melodia) + skill = 3; + assert(skill >= 0 && skill <= 3); + return skill; +} + +bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const +{ + if(!getArt(ArtifactPosition::SPELLBOOK)) //if hero has no spellbook + return false; + + if (spell->isSpecialSpell()) + { + if (vstd::contains(spells, spell->id)) + {//hero has this spell in spellbook + logGlobal->errorStream() << "Special spell in spellbook "<name; + } + + if (hasBonusOfType(Bonus::SPELL, spell->id)) + return true; + + return false; + } + else + { + if(vstd::contains(spells, spell->id) //hero has this spell in spellbook + || (spell->air && hasBonusOfType(Bonus::AIR_SPELLS)) // this is air spell and hero can cast all air spells + || (spell->fire && hasBonusOfType(Bonus::FIRE_SPELLS)) // this is fire spell and hero can cast all fire spells + || (spell->water && hasBonusOfType(Bonus::WATER_SPELLS)) // this is water spell and hero can cast all water spells + || (spell->earth && hasBonusOfType(Bonus::EARTH_SPELLS)) // this is earth spell and hero can cast all earth spells + || hasBonusOfType(Bonus::SPELL, spell->id) + || hasBonusOfType(Bonus::SPELLS_OF_LEVEL, spell->level) + ) + return true; + + return false; + } +} + +/** + * Calculates what creatures and how many to be raised from a battle. + * @param battleResult The results of the battle. + * @return Returns a pair with the first value indicating the ID of the creature + * type and second value the amount. Both values are returned as -1 if necromancy + * could not be applied. + */ +CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &battleResult) const +{ + const ui8 necromancyLevel = getSecSkillLevel(SecondarySkill::NECROMANCY); + + // Hero knows necromancy or has Necromancer Cloak + if (necromancyLevel > 0 || hasBonusOfType(Bonus::IMPROVED_NECROMANCY)) + { + double necromancySkill = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::NECROMANCY)/100.0; + vstd::amin(necromancySkill, 1.0); //it's impossible to raise more creatures than all... + const std::map &casualties = battleResult.casualties[!battleResult.winner]; + ui32 raisedUnits = 0; + + // Figure out what to raise and how many. + const CreatureID creatureTypes[] = {CreatureID::SKELETON, CreatureID::WALKING_DEAD, CreatureID::WIGHTS, CreatureID::LICHES}; + const bool improvedNecromancy = hasBonusOfType(Bonus::IMPROVED_NECROMANCY); + const CCreature *raisedUnitType = VLC->creh->creatures[creatureTypes[improvedNecromancy ? necromancyLevel : 0]]; + const ui32 raisedUnitHP = raisedUnitType->valOfBonuses(Bonus::STACK_HEALTH); + + //calculate creatures raised from each defeated stack + for (auto & casualtie : casualties) + { + // Get lost enemy hit points convertible to units. + CCreature * c = VLC->creh->creatures[casualtie.first]; + + const ui32 raisedHP = c->valOfBonuses(Bonus::STACK_HEALTH) * casualtie.second * necromancySkill; + raisedUnits += std::min(raisedHP / raisedUnitHP, casualtie.second * necromancySkill); //limit to % of HP and % of original stack count + } + + // Make room for new units. + SlotID slot = getSlotFor(raisedUnitType->idNumber); + if (slot == SlotID()) + { + // If there's no room for unit, try it's upgraded version 2/3rds the size. + raisedUnitType = VLC->creh->creatures[*raisedUnitType->upgrades.begin()]; + raisedUnits = (raisedUnits*2)/3; + + slot = getSlotFor(raisedUnitType->idNumber); + } + if (raisedUnits <= 0) + raisedUnits = 1; + + return CStackBasicDescriptor(raisedUnitType->idNumber, raisedUnits); + } + + return CStackBasicDescriptor(); +} + +/** + * Show the necromancy dialog with information about units raised. + * @param raisedStack Pair where the first element represents ID of the raised creature + * and the second element the amount. + */ +void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const +{ + InfoWindow iw; + iw.soundID = soundBase::pickup01 + cb->gameState()->getRandomGenerator().nextInt(6); + iw.player = tempOwner; + iw.components.push_back(Component(raisedStack)); + + if (raisedStack.count > 1) // Practicing the dark arts of necromancy, ... (plural) + { + iw.text.addTxt(MetaString::GENERAL_TXT, 145); + iw.text.addReplacement(raisedStack.count); + } + else // Practicing the dark arts of necromancy, ... (singular) + { + iw.text.addTxt(MetaString::GENERAL_TXT, 146); + } + iw.text.addReplacement(raisedStack); + + cb->showInfoDialog(&iw); +} + +int3 CGHeroInstance::getSightCenter() const +{ + return getPosition(false); +} + +int CGHeroInstance::getSightRadious() const +{ + return 5 + getSecSkillLevel(SecondarySkill::SCOUTING) + valOfBonuses(Bonus::SIGHT_RADIOUS); //default + scouting +} + +si32 CGHeroInstance::manaRegain() const +{ + if (hasBonusOfType(Bonus::FULL_MANA_REGENERATION)) + return manaLimit(); + + return 1 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 8) + valOfBonuses(Bonus::MANA_REGENERATION); //1 + Mysticism level +} + +// /** +// * Places an artifact in hero's backpack. If it's a big artifact equips it +// * or discards it if it cannot be equipped. +// */ +// void CGHeroInstance::giveArtifact (ui32 aid) //use only for fixed artifacts +// { +// CArtifact * const artifact = VLC->arth->artifacts[aid]; //pointer to constant object +// CArtifactInstance *ai = CArtifactInstance::createNewArtifactInstance(artifact); +// ai->putAt(this, ai->firstAvailableSlot(this)); +// } + +int CGHeroInstance::getBoatType() const +{ + switch(type->heroClass->getAlignment()) + { + case EAlignment::GOOD: + return 1; + case EAlignment::EVIL: + return 0; + case EAlignment::NEUTRAL: + return 2; + default: + throw std::runtime_error("Wrong alignment!"); + } +} + +void CGHeroInstance::getOutOffsets(std::vector &offsets) const +{ + static int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) }; + for (auto & dir : dirs) + offsets += dir; +} + +int CGHeroInstance::getSpellCost(const CSpell *sp) const +{ + return sp->getCost(getSpellSchoolLevel(sp)); +} + +void CGHeroInstance::pushPrimSkill( PrimarySkill::PrimarySkill which, int val ) +{ + assert(!hasBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, which) + .And(Selector::sourceType(Bonus::HERO_BASE_SKILL)))); + addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::PRIMARY_SKILL, Bonus::HERO_BASE_SKILL, val, id.getNum(), which)); +} + +EAlignment::EAlignment CGHeroInstance::getAlignment() const +{ + return type->heroClass->getAlignment(); +} + +void CGHeroInstance::initExp() +{ + exp = cb->gameState()->getRandomGenerator().nextInt(40, 89); +} + +std::string CGHeroInstance::nodeName() const +{ + return "Hero " + name; +} + +void CGHeroInstance::putArtifact(ArtifactPosition pos, CArtifactInstance *art) +{ + assert(!getArt(pos)); + art->putAt(ArtifactLocation(this, pos)); +} + +void CGHeroInstance::putInBackpack(CArtifactInstance *art) +{ + putArtifact(art->firstBackpackSlot(this), art); +} + +bool CGHeroInstance::hasSpellbook() const +{ + return getArt(ArtifactPosition::SPELLBOOK); +} + +void CGHeroInstance::deserializationFix() +{ + artDeserializationFix(this); + + for (auto hs : specialty) + { + attachTo (hs); + } +} + +CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs) +{ + if(visitedTown) + { + if(inTownGarrison) + return visitedTown; + else + return &visitedTown->townAndVis; + } + else + return CArmedInstance::whereShouldBeAttached(gs); +} + +int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/) const +{ + if(hasBonusOfType(Bonus::FREE_SHIP_BOARDING)) + return (MPsBefore - basicCost) * static_cast(maxMovePoints(disembark)) / maxMovePoints(!disembark); + + return 0; //take all MPs otherwise +} + +CGHeroInstance::ECanDig CGHeroInstance::diggingStatus() const +{ + if(movement < maxMovePoints(true)) + return LACK_OF_MOVEMENT; + else if(cb->getTile(getPosition(false))->terType == ETerrainType::WATER) + return WRONG_TERRAIN; + else + { + const TerrainTile *t = cb->getTile(getPosition()); + //TODO look for hole + //CGI->mh->getTerrainDescr(h->getPosition(false), hlp, false); + if(/*hlp.length() || */t->blockingObjects.size() > 1) + return TILE_OCCUPIED; + else + return CAN_DIG; + } +} + +ArtBearer::ArtBearer CGHeroInstance::bearerType() const +{ + return ArtBearer::HERO; +} + +std::vector CGHeroInstance::getLevelUpProposedSecondarySkills() const +{ + std::vector obligatorySkills; //hero is offered magic school or wisdom if possible + if (!skillsInfo.wisdomCounter) + { + if (cb->isAllowed(2, SecondarySkill::WISDOM) && !getSecSkillLevel(SecondarySkill::WISDOM)) + obligatorySkills.push_back(SecondarySkill::WISDOM); + } + if (!skillsInfo.magicSchoolCounter) + { + std::vector ss; + ss += SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC; + + std::shuffle(ss.begin(), ss.end(), skillsInfo.rand.getStdGenerator()); + + for (auto skill : ss) + { + if (cb->isAllowed(2, skill) && !getSecSkillLevel(skill)) //only schools hero doesn't know yet + { + obligatorySkills.push_back(skill); + break; //only one + } + } + } + + std::vector skills; + //picking sec. skills for choice + std::set basicAndAdv, expert, none; + for(int i=0;iisAllowed(2,i)) + none.insert(SecondarySkill(i)); + + for(auto & elem : secSkills) + { + if(elem.second < SecSkillLevel::EXPERT) + basicAndAdv.insert(elem.first); + else + expert.insert(elem.first); + none.erase(elem.first); + } + for (auto s : obligatorySkills) //don't duplicate them + { + none.erase (s); + basicAndAdv.erase (s); + expert.erase (s); + } + + //first offered skill: + // 1) give obligatory skill + // 2) give any other new skill + // 3) upgrade existing + if (canLearnSkill() && obligatorySkills.size() > 0) + { + skills.push_back (obligatorySkills[0]); + } + else if(none.size() && canLearnSkill()) //hero have free skill slot + { + skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //new skill + none.erase(skills.back()); + } + else if(!basicAndAdv.empty()) + { + skills.push_back(type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand)); //upgrade existing + basicAndAdv.erase(skills.back()); + } + + //second offered skill: + //1) upgrade existing + //2) give obligatory skill + //3) give any other new skill + if(!basicAndAdv.empty()) + { + SecondarySkill s = type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand);//upgrade existing + skills.push_back(s); + basicAndAdv.erase(s); + } + else if (canLearnSkill() && obligatorySkills.size() > 1) + { + skills.push_back (obligatorySkills[1]); + } + else if(none.size() && canLearnSkill()) + { + skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //give new skill + none.erase(skills.back()); + } + + return skills; +} + +PrimarySkill::PrimarySkill CGHeroInstance::nextPrimarySkill() const +{ + assert(gainsLevel()); + int randomValue = cb->gameState()->getRandomGenerator().nextInt(99), pom = 0, primarySkill = 0; + const auto & skillChances = (level > 9) ? type->heroClass->primarySkillLowLevel : type->heroClass->primarySkillHighLevel; + + for(; primarySkill < GameConstants::PRIMARY_SKILLS; ++primarySkill) + { + pom += skillChances[primarySkill]; + if(randomValue < pom) + { + break; + } + } + + logGlobal->traceStream() << "The hero gets the primary skill " << primarySkill << " with a probability of " << randomValue << "%."; + return static_cast(primarySkill); +} + +boost::optional CGHeroInstance::nextSecondarySkill() const +{ + assert(gainsLevel()); + + boost::optional chosenSecondarySkill; + const auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(); + if(!proposedSecondarySkills.empty()) + { + std::vector learnedSecondarySkills; + for(auto secondarySkill : proposedSecondarySkills) + { + if(getSecSkillLevel(secondarySkill) > 0) + { + learnedSecondarySkills.push_back(secondarySkill); + } + } + + auto & rand = cb->gameState()->getRandomGenerator(); + if(learnedSecondarySkills.empty()) + { + // there are only new skills to learn, so choose anyone of them + chosenSecondarySkill = *RandomGeneratorUtil::nextItem(proposedSecondarySkills, rand); + } + else + { + // preferably upgrade a already learned secondary skill + chosenSecondarySkill = *RandomGeneratorUtil::nextItem(learnedSecondarySkills, rand); + } + } + return chosenSecondarySkill; +} + +void CGHeroInstance::setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs) +{ + if(primarySkill < PrimarySkill::EXPERIENCE) + { + Bonus * skill = getBonusLocalFirst(Selector::type(Bonus::PRIMARY_SKILL) + .And(Selector::subtype(primarySkill)) + .And(Selector::sourceType(Bonus::HERO_BASE_SKILL))); + assert(skill); + + if(abs) + { + skill->val = value; + } + else + { + skill->val += value; + } + } + else if(primarySkill == PrimarySkill::EXPERIENCE) + { + if(abs) + { + exp = value; + } + else + { + exp += value; + } + } +} + +bool CGHeroInstance::gainsLevel() const +{ + return exp >= VLC->heroh->reqExp(level+1); +} + +void CGHeroInstance::levelUp(std::vector skills) +{ + ++level; + + //deterministic secondary skills + skillsInfo.magicSchoolCounter = (skillsInfo.magicSchoolCounter + 1) % maxlevelsToMagicSchool(); + skillsInfo.wisdomCounter = (skillsInfo.wisdomCounter + 1) % maxlevelsToWisdom(); + if(vstd::contains(skills, SecondarySkill::WISDOM)) + { + skillsInfo.resetWisdomCounter(); + } + + SecondarySkill spellSchools[] = { + SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC}; + for(auto skill : spellSchools) + { + if(vstd::contains(skills, skill)) + { + skillsInfo.resetMagicSchoolCounter(); + break; + } + } + + //specialty + Updatespecialty(); +} + +void CGHeroInstance::levelUpAutomatically() +{ + while(gainsLevel()) + { + const auto primarySkill = nextPrimarySkill(); + setPrimarySkill(primarySkill, 1, false); + + auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(); + + const auto secondarySkill = nextSecondarySkill(); + if(secondarySkill) + { + setSecSkillLevel(*secondarySkill, 1, false); + } + + //TODO why has the secondary skills to be passed to the method? + levelUp(proposedSecondarySkills); + } +} diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h new file mode 100644 index 000000000..327c0725b --- /dev/null +++ b/lib/mapObjects/CGHeroInstance.h @@ -0,0 +1,227 @@ +#pragma once + +#include "CObjectHandler.h" +#include "CArmedInstance.h" + +#include "../CArtHandler.h" // For CArtifactSet +#include "../CRandomGenerator.h" + +/* + * CGHeroInstance.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 + * + */ + +class CHero; +class CGBoat; + +class CGHeroPlaceholder : public CGObjectInstance +{ +public: + //subID stores id of hero type. If it's 0xff then following field is used + ui8 power; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & power; + } +}; + + +class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet +{ +public: + enum ECanDig + { + CAN_DIG, LACK_OF_MOVEMENT, WRONG_TERRAIN, TILE_OCCUPIED + }; + ////////////////////////////////////////////////////////////////////////// + + ui8 moveDir; //format: 123 + // 8 4 + // 765 + mutable ui8 isStanding, tacticFormationEnabled; + + ////////////////////////////////////////////////////////////////////////// + + ConstTransitivePtr type; + TExpType exp; //experience points + ui32 level; //current level of hero + std::string name; //may be custom + std::string biography; //if custom + si32 portrait; //may be custom + si32 mana; // remaining spell points + std::vector > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert); if hero has ability (-1, -1) it meansthat it should have default secondary abilities + ui32 movement; //remaining movement points + ui8 sex; + bool inTownGarrison; // if hero is in town garrison + ConstTransitivePtr visitedTown; //set if hero is visiting town or in the town garrison + ConstTransitivePtr commander; + const CGBoat *boat; //set to CGBoat when sailing + + + //std::vector artifacts; //hero's artifacts from bag + //std::map artifWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 + std::set spells; //known spells (spell IDs) + std::set visitedObjects; + + struct DLL_LINKAGE Patrol + { + Patrol(){patrolling=false;patrolRadious=-1;}; + bool patrolling; + ui32 patrolRadious; + template void serialize(Handler &h, const int version) + { + h & patrolling & patrolRadious; + } + } patrol; + + struct DLL_LINKAGE HeroSpecial : CBonusSystemNode + { + bool growsWithLevel; + + HeroSpecial(){growsWithLevel = false;}; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & growsWithLevel; + } + }; + + std::vector specialty; + + struct DLL_LINKAGE SecondarySkillsInfo + { + //skills are determined, initialized at map start + //FIXME remove mutable + mutable CRandomGenerator rand; + ui8 magicSchoolCounter; + ui8 wisdomCounter; + + void resetMagicSchoolCounter(); + void resetWisdomCounter(); + + template void serialize(Handler &h, const int version) + { + h & magicSchoolCounter & wisdomCounter & rand; + } + } skillsInfo; + + int3 getSightCenter() const; //"center" tile from which the sight distance is calculated + int getSightRadious() const; //sight distance (should be used if player-owned structure) + ////////////////////////////////////////////////////////////////////////// + + int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral + void getOutOffsets(std::vector &offsets) const; //offsets to obj pos when we boat can be placed + + ////////////////////////////////////////////////////////////////////////// + + bool hasSpellbook() const; + EAlignment::EAlignment getAlignment() const; + const std::string &getBiography() const; + bool needsLastStack()const; + ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling + ui32 getLowestCreatureSpeed() const; + int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' + si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day + bool canWalkOnSea() const; + int getCurrentLuck(int stack=-1, bool town=false) const; + int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored + + // ----- primary and secondary skill, experience, level handling ----- + + /// Returns true if hero has lower level than should upon his experience. + bool gainsLevel() const; + + /// Returns the next primary skill on level up. Can only be called if hero can gain a level up. + PrimarySkill::PrimarySkill nextPrimarySkill() const; + + /// Returns the next secondary skill randomly on level up. Can only be called if hero can gain a level up. + boost::optional nextSecondarySkill() const; + + /// Gets 0, 1 or 2 secondary skills which are proposed on hero level up. + std::vector getLevelUpProposedSecondarySkills() const; + + ui8 getSecSkillLevel(SecondarySkill skill) const; //0 - no skill + + /// Returns true if hero has free secondary skill slot. + bool canLearnSkill() const; + + void setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs); + void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value + void levelUp(std::vector skills); + + int maxMovePoints(bool onLand) const; + int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const; + + //int getSpellSecLevel(int spell) const; //returns level of secondary ability (fire, water, earth, air magic) known to this hero and applicable to given spell; -1 if error + static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest + double getFightingStrength() const; // takes attack / defense skill into account + double getMagicStrength() const; // takes knowledge / spell power skill into account + double getHeroStrength() const; // includes fighting and magic strength + ui64 getTotalStrength() const; // includes fighting strength and army strength + TExpType calculateXp(TExpType exp) const; //apply learning skill + ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const; //returns level on which given spell would be cast by this hero (0 - none, 1 - basic etc); optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic, + bool canCastThisSpell(const CSpell * spell) const; //determines if this hero can cast given spell; takes into account existing spell in spellbook, existing spellbook and artifact bonuses + CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const; + void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const; + ECanDig diggingStatus() const; //0 - can dig; 1 - lack of movement; 2 - + + ////////////////////////////////////////////////////////////////////////// + + void setType(si32 ID, si32 subID); + + void initHero(); + void initHero(HeroTypeID SUBID); + + void putArtifact(ArtifactPosition pos, CArtifactInstance *art); + void putInBackpack(CArtifactInstance *art); + void initExp(); + void initArmy(IArmyDescriptor *dst = nullptr); + //void giveArtifact (ui32 aid); + void pushPrimSkill(PrimarySkill::PrimarySkill which, int val); + ui8 maxlevelsToMagicSchool() const; + ui8 maxlevelsToWisdom() const; + void Updatespecialty(); + void recreateSecondarySkillsBonuses(); + void updateSkill(SecondarySkill which, int val); + + CGHeroInstance(); + virtual ~CGHeroInstance(); + ////////////////////////////////////////////////////////////////////////// + // + ArtBearer::ArtBearer bearerType() const override; + ////////////////////////////////////////////////////////////////////////// + + CBonusSystemNode *whereShouldBeAttached(CGameState *gs) override; + std::string nodeName() const override; + void deserializationFix(); + + void initObj() override; + void onHeroVisit(const CGHeroInstance * h) const override; + const std::string & getHoverText() const override; +protected: + void setPropertyDer(ui8 what, ui32 val) override;//synchr + +private: + void levelUpAutomatically(); + +public: + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + h & exp & level & name & biography & portrait & mana & secSkills & movement + & sex & inTownGarrison & spells & patrol & moveDir & skillsInfo; + h & visitedTown & boat; + h & type & specialty & commander; + BONUS_TREE_DESERIALIZATION_FIX + //visitied town pointer will be restored by map serialization method + } +}; diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp new file mode 100644 index 000000000..98205974e --- /dev/null +++ b/lib/mapObjects/CGMarket.cpp @@ -0,0 +1,335 @@ +/* + * + * CGMarket.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CGMarket.h" + +#include "../NetPacks.h" +#include "../CGeneralTextHandler.h" + +using namespace boost::assign; + +///helpers +static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1) +{ + OpenWindow ow; + ow.window = type; + ow.id1 = id1; + ow.id2 = id2; + IObjectInterface::cb->sendAndApply(&ow); +} + +bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode::EMarketMode mode) const +{ + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + { + double effectiveness = std::min((getMarketEfficiency() + 1.0) / 20.0, 0.5); + + double r = VLC->objh->resVals[id1], //value of given resource + g = VLC->objh->resVals[id2] / effectiveness; //value of wanted resource + + if(r>g) //if given resource is more expensive than wanted + { + val2 = ceil(r / g); + val1 = 1; + } + else //if wanted resource is more expensive + { + val1 = (g / r) + 0.5; + val2 = 1; + } + } + break; + case EMarketMode::CREATURE_RESOURCE: + { + const double effectivenessArray[] = {0.0, 0.3, 0.45, 0.50, 0.65, 0.7, 0.85, 0.9, 1.0}; + double effectiveness = effectivenessArray[std::min(getMarketEfficiency(), 8)]; + + double r = VLC->creh->creatures[id1]->cost[6], //value of given creature in gold + g = VLC->objh->resVals[id2] / effectiveness; //value of wanted resource + + if(r>g) //if given resource is more expensive than wanted + { + val2 = ceil(r / g); + val1 = 1; + } + else //if wanted resource is more expensive + { + val1 = (g / r) + 0.5; + val2 = 1; + } + } + break; + case EMarketMode::RESOURCE_PLAYER: + val1 = 1; + val2 = 1; + break; + case EMarketMode::RESOURCE_ARTIFACT: + { + double effectiveness = std::min((getMarketEfficiency() + 3.0) / 20.0, 0.6); + double r = VLC->objh->resVals[id1], //value of offered resource + g = VLC->arth->artifacts[id2]->price / effectiveness; //value of bought artifact in gold + + if(id1 != 6) //non-gold prices are doubled + r /= 2; + + val1 = std::max(1, (int)((g / r) + 0.5)); //don't sell arts for less than 1 resource + val2 = 1; + } + break; + case EMarketMode::ARTIFACT_RESOURCE: + { + double effectiveness = std::min((getMarketEfficiency() + 3.0) / 20.0, 0.6); + double r = VLC->arth->artifacts[id1]->price * effectiveness, + g = VLC->objh->resVals[id2]; + +// if(id2 != 6) //non-gold prices are doubled +// r /= 2; + + val1 = 1; + val2 = std::max(1, (int)((r / g) + 0.5)); //at least one resource is given in return + } + break; + case EMarketMode::CREATURE_EXP: + { + val1 = 1; + val2 = (VLC->creh->creatures[id1]->AIValue / 40) * 5; + } + break; + case EMarketMode::ARTIFACT_EXP: + { + val1 = 1; + + int givenClass = VLC->arth->artifacts[id1]->getArtClassSerial(); + if(givenClass < 0 || givenClass > 3) + { + val2 = 0; + return false; + } + + static const int expPerClass[] = {1000, 1500, 3000, 6000}; + val2 = expPerClass[givenClass]; + } + break; + default: + assert(0); + return false; + } + + return true; +} + +bool IMarket::allowsTrade(EMarketMode::EMarketMode mode) const +{ + return false; +} + +int IMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const +{ + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + case EMarketMode::ARTIFACT_RESOURCE: + case EMarketMode::CREATURE_RESOURCE: + return -1; + default: + return 1; + } +} + +std::vector IMarket::availableItemsIds(EMarketMode::EMarketMode mode) const +{ + std::vector ret; + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + case EMarketMode::ARTIFACT_RESOURCE: + case EMarketMode::CREATURE_RESOURCE: + for (int i = 0; i < 7; i++) + ret.push_back(i); + } + return ret; +} + +const IMarket * IMarket::castFrom(const CGObjectInstance *obj, bool verbose /*= true*/) +{ + switch(obj->ID) + { + case Obj::TOWN: + return static_cast(obj); + case Obj::ALTAR_OF_SACRIFICE: + case Obj::BLACK_MARKET: + case Obj::TRADING_POST: + case Obj::TRADING_POST_SNOW: + case Obj::FREELANCERS_GUILD: + return static_cast(obj); + case Obj::UNIVERSITY: + return static_cast(obj); + default: + if(verbose) + logGlobal->errorStream() << "Cannot cast to IMarket object with ID " << obj->ID; + return nullptr; + } +} + +IMarket::IMarket(const CGObjectInstance *O) + :o(O) +{ + +} + +std::vector IMarket::availableModes() const +{ + std::vector ret; + for (int i = 0; i < EMarketMode::MARTKET_AFTER_LAST_PLACEHOLDER; i++) + if(allowsTrade((EMarketMode::EMarketMode)i)) + ret.push_back((EMarketMode::EMarketMode)i); + + return ret; +} + +void CGMarket::onHeroVisit(const CGHeroInstance * h) const +{ + openWindow(OpenWindow::MARKET_WINDOW,id.getNum(),h->id.getNum()); +} + +int CGMarket::getMarketEfficiency() const +{ + return 5; +} + +bool CGMarket::allowsTrade(EMarketMode::EMarketMode mode) const +{ + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + case EMarketMode::RESOURCE_PLAYER: + switch(ID) + { + case Obj::TRADING_POST: + case Obj::TRADING_POST_SNOW: + return true; + default: + return false; + } + case EMarketMode::CREATURE_RESOURCE: + return ID == Obj::FREELANCERS_GUILD; + //case ARTIFACT_RESOURCE: + case EMarketMode::RESOURCE_ARTIFACT: + return ID == Obj::BLACK_MARKET; + case EMarketMode::ARTIFACT_EXP: + case EMarketMode::CREATURE_EXP: + return ID == Obj::ALTAR_OF_SACRIFICE; //TODO? check here for alignment of visiting hero? - would not be coherent with other checks here + case EMarketMode::RESOURCE_SKILL: + return ID == Obj::UNIVERSITY; + default: + return false; + } +} + +int CGMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const +{ + return -1; +} + +std::vector CGMarket::availableItemsIds(EMarketMode::EMarketMode mode) const +{ + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + case EMarketMode::RESOURCE_PLAYER: + return IMarket::availableItemsIds(mode); + default: + return std::vector(); + } +} + +CGMarket::CGMarket() + :IMarket(this) +{ +} + +std::vector CGBlackMarket::availableItemsIds(EMarketMode::EMarketMode mode) const +{ + switch(mode) + { + case EMarketMode::ARTIFACT_RESOURCE: + return IMarket::availableItemsIds(mode); + case EMarketMode::RESOURCE_ARTIFACT: + { + std::vector ret; + for(const CArtifact *a : artifacts) + if(a) + ret.push_back(a->id); + else + ret.push_back(-1); + return ret; + } + default: + return std::vector(); + } +} + +void CGBlackMarket::newTurn() const +{ + if(cb->getDate(Date::DAY_OF_MONTH) != 1) //new month + return; + + SetAvailableArtifacts saa; + saa.id = id.getNum(); + cb->pickAllowedArtsSet(saa.arts); + cb->sendAndApply(&saa); +} + +void CGUniversity::initObj() +{ + std::vector toChoose; + for(int i = 0; i < GameConstants::SKILL_QUANTITY; ++i) + { + if(cb->isAllowed(2, i)) + { + toChoose.push_back(i); + } + } + if(toChoose.size() < 4) + { + logGlobal->warnStream()<<"Warning: less then 4 available skills was found by University initializer!"; + return; + } + + // get 4 skills + for(int i = 0; i < 4; ++i) + { + // move randomly one skill to selected and remove from list + auto it = RandomGeneratorUtil::nextItem(toChoose, cb->gameState()->getRandomGenerator()); + skills.push_back(*it); + toChoose.erase(it); + } +} + +std::vector CGUniversity::availableItemsIds(EMarketMode::EMarketMode mode) const +{ + switch (mode) + { + case EMarketMode::RESOURCE_SKILL: + return skills; + + default: + return std::vector (); + } +} + +void CGUniversity::onHeroVisit(const CGHeroInstance * h) const +{ + openWindow(OpenWindow::UNIVERSITY_WINDOW,id.getNum(),h->id.getNum()); +} diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h new file mode 100644 index 000000000..8f010a705 --- /dev/null +++ b/lib/mapObjects/CGMarket.h @@ -0,0 +1,88 @@ +#pragma once + +#include "CObjectHandler.h" + +/* + * CGMarket.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 + * + */ + +class DLL_LINKAGE IMarket +{ +public: + const CGObjectInstance *o; + + IMarket(const CGObjectInstance *O); + virtual ~IMarket() {} + + virtual int getMarketEfficiency() const =0; + virtual bool allowsTrade(EMarketMode::EMarketMode mode) const; + virtual int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const; //-1 if unlimited + virtual std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; + + bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode::EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units + std::vector availableModes() const; + + static const IMarket *castFrom(const CGObjectInstance *obj, bool verbose = true); + + template void serialize(Handler &h, const int version) + { + h & o; + } +}; + +class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket +{ +public: + CGMarket(); + ///IObjectIntercae + void onHeroVisit(const CGHeroInstance * h) const override; //open trading window + + ///IMarket + int getMarketEfficiency() const override; + bool allowsTrade(EMarketMode::EMarketMode mode) const override; + int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const override; //-1 if unlimited + std::vector availableItemsIds(EMarketMode::EMarketMode mode) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGBlackMarket : public CGMarket +{ +public: + std::vector artifacts; //available artifacts + + void newTurn() const override; //reset artifacts for black market every month + std::vector availableItemsIds(EMarketMode::EMarketMode mode) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & artifacts; + } +}; + +class DLL_LINKAGE CGUniversity : public CGMarket +{ +public: + std::vector skills; //available skills + + std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; + void initObj() override;//set skills for trade + void onHeroVisit(const CGHeroInstance * h) const override; //open window + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & skills; + } +}; diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp new file mode 100644 index 000000000..8f102e0a0 --- /dev/null +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -0,0 +1,364 @@ +/* + * CGPandoraBox.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CGPandoraBox.h" + +#include "../NetPacks.h" +#include "../CSoundBase.h" + +#include "../CSpellHandler.h" + +using namespace boost::assign; + +///helpers +static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) +{ + InfoWindow iw; + iw.soundID = soundID; + iw.player = playerID; + iw.text.addTxt(MetaString::ADVOB_TXT,txtID); + IObjectInterface::cb->sendAndApply(&iw); +} + +static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID) +{ + const PlayerColor playerID = h->getOwner(); + showInfoDialog(playerID,txtID,soundID); +} + +void CGPandoraBox::initObj() +{ + blockVisit = (ID==Obj::PANDORAS_BOX); //block only if it's really pandora's box (events also derive from that class) + hasGuardians = stacks.size(); +} + +void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const +{ + BlockingDialog bd (true, false); + bd.player = h->getOwner(); + bd.soundID = soundBase::QUEST; + bd.text.addTxt (MetaString::ADVOB_TXT, 14); + cb->showBlockingDialog (&bd); +} + +void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const +{ + cb->removeAfterVisit(this); + + InfoWindow iw; + iw.player = h->getOwner(); + + bool changesPrimSkill = false; + for (auto & elem : primskills) + { + if(elem) + { + changesPrimSkill = true; + break; + } + } + + if(gainedExp || changesPrimSkill || abilities.size()) + { + TExpType expVal = h->calculateXp(gainedExp); + //getText(iw,afterBattle,175,h); //wtf? + iw.text.addTxt(MetaString::ADVOB_TXT, 175); //%s learns something + iw.text.addReplacement(h->name); + + if(expVal) + iw.components.push_back(Component(Component::EXPERIENCE,0,expVal,0)); + for(int i=0; ishowInfoDialog(&iw); + + //give sec skills + for(int i=0; igetSecSkillLevel(abilities[i]); + + if( (curLev && curLev < abilityLevels[i]) || (h->canLearnSkill() )) + { + cb->changeSecSkill(h,abilities[i],abilityLevels[i],true); + } + } + + //give prim skills + for(int i=0; ichangePrimSkill(h,static_cast(i),primskills[i],false); + + assert(!cb->isVisitCoveredByAnotherQuery(this, h)); + + //give exp + if(expVal) + cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false); + } + + if(!cb->isVisitCoveredByAnotherQuery(this, h)) + giveContentsAfterExp(h); + //Otherwise continuation occurs via post-level-up callback. +} + +void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const +{ + bool hadGuardians = hasGuardians; //copy, because flag will be emptied after issuing first post-battle message + + std::string msg = message; //in case box is removed in the meantime + InfoWindow iw; + iw.player = h->getOwner(); + + if(spells.size()) + { + std::set spellsToGive; + iw.components.clear(); + if (spells.size() > 1) + { + iw.text.addTxt(MetaString::ADVOB_TXT, 188); //%s learns spells + } + else + { + iw.text.addTxt(MetaString::ADVOB_TXT, 184); //%s learns a spell + } + iw.text.addReplacement(h->name); + std::vector > * sp = &VLC->spellh->objects; + for(auto i=spells.cbegin(); i != spells.cend(); i++) + { + if ((*sp)[*i]->level <= h->getSecSkillLevel(SecondarySkill::WISDOM) + 2) //enough wisdom + { + iw.components.push_back(Component(Component::SPELL,*i,0,0)); + spellsToGive.insert(*i); + } + } + if(!spellsToGive.empty()) + { + cb->changeSpells(h,true,spellsToGive); + cb->showInfoDialog(&iw); + } + } + + if(manaDiff) + { + getText(iw,hadGuardians,manaDiff,176,177,h); + iw.components.push_back(Component(Component::PRIM_SKILL,5,manaDiff,0)); + cb->showInfoDialog(&iw); + cb->setManaPoints(h->id, h->mana + manaDiff); + } + + if(moraleDiff) + { + getText(iw,hadGuardians,moraleDiff,178,179,h); + iw.components.push_back(Component(Component::MORALE,0,moraleDiff,0)); + cb->showInfoDialog(&iw); + GiveBonus gb; + gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::MORALE,Bonus::OBJECT,moraleDiff,id.getNum(),""); + gb.id = h->id.getNum(); + cb->giveHeroBonus(&gb); + } + + if(luckDiff) + { + getText(iw,hadGuardians,luckDiff,180,181,h); + iw.components.push_back(Component(Component::LUCK,0,luckDiff,0)); + cb->showInfoDialog(&iw); + GiveBonus gb; + gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,luckDiff,id.getNum(),""); + gb.id = h->id.getNum(); + cb->giveHeroBonus(&gb); + } + + iw.components.clear(); + iw.text.clear(); + for(int i=0; ishowInfoDialog(&iw); + } + + iw.components.clear(); + iw.text.clear(); + for(int i=0; i 0) + iw.components.push_back(Component(Component::RESOURCE,i,resources[i],0)); + } + if(iw.components.size()) + { + getText(iw,hadGuardians,183,h); + cb->showInfoDialog(&iw); + } + + iw.components.clear(); + // getText(iw,afterBattle,183,h); + iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure + iw.text.addReplacement(h->name); + for(auto & elem : artifacts) + { + iw.components.push_back(Component(Component::ARTIFACT,elem,0,0)); + if(iw.components.size() >= 14) + { + cb->showInfoDialog(&iw); + iw.components.clear(); + iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - once more? + iw.text.addReplacement(h->name); + } + } + if(iw.components.size()) + { + cb->showInfoDialog(&iw); + } + + for(int i=0; igiveResource(h->getOwner(),static_cast(i),resources[i]); + + for(auto & elem : artifacts) + cb->giveHeroNewArtifact(h, VLC->arth->artifacts[elem],ArtifactPosition::FIRST_AVAILABLE); + + iw.components.clear(); + iw.text.clear(); + + if (creatures.Slots().size()) + { //this part is taken straight from creature bank + MetaString loot; + for(auto & elem : creatures.Slots()) + { //build list of joined creatures + iw.components.push_back(Component(*elem.second)); + loot << "%s"; + loot.addReplacement(*elem.second); + } + + if (creatures.Slots().size() == 1 && creatures.Slots().begin()->second->count == 1) + iw.text.addTxt(MetaString::ADVOB_TXT, 185); + else + iw.text.addTxt(MetaString::ADVOB_TXT, 186); + + iw.text.addReplacement(loot.buildList()); + iw.text.addReplacement(h->name); + + cb->showInfoDialog(&iw); + cb->giveCreatures(this, h, creatures, true); + } + if(!hasGuardians && msg.size()) + { + iw.text << msg; + cb->showInfoDialog(&iw); + } +} + +void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const +{ + if(afterBattle || !message.size()) + { + iw.text.addTxt(MetaString::ADVOB_TXT,text);//%s has lost treasure. + iw.text.addReplacement(h->name); + } + else + { + iw.text << message; + afterBattle = true; + } +} + +void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const +{ + iw.components.clear(); + iw.text.clear(); + if(afterBattle || !message.size()) + { + iw.text.addTxt(MetaString::ADVOB_TXT,val < 0 ? negative : positive); //%s's luck takes a turn for the worse / %s's luck increases + iw.text.addReplacement(h->name); + } + else + { + iw.text << message; + afterBattle = true; + } +} + +void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner) + return; + + giveContentsUpToExp(hero); +} + +void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if (answer) + { + if (stacksCount() > 0) //if pandora's box is protected by army + { + showInfoDialog(hero,16,0); + cb->startBattleI(hero, this); //grants things after battle + } + else if (message.size() == 0 && resources.size() == 0 + && primskills.size() == 0 && abilities.size() == 0 + && abilityLevels.size() == 0 && artifacts.size() == 0 + && spells.size() == 0 && creatures.Slots().size() > 0 + && gainedExp == 0 && manaDiff == 0 && moraleDiff == 0 && luckDiff == 0) //if it gives nothing without battle + { + showInfoDialog(hero,15,0); + cb->removeObject(this); + } + else //if it gives something without battle + { + giveContentsUpToExp(hero); + } + } +} + +void CGPandoraBox::heroLevelUpDone(const CGHeroInstance *hero) const +{ + giveContentsAfterExp(hero); +} + +void CGEvent::onHeroVisit( const CGHeroInstance * h ) const +{ + if(!(availableFor & (1 << h->tempOwner.getNum()))) + return; + if(cb->getPlayerSettings(h->tempOwner)->playerID) + { + if(humanActivate) + activated(h); + } + else if(computerActivate) + activated(h); +} + +void CGEvent::activated( const CGHeroInstance * h ) const +{ + if(stacksCount() > 0) + { + InfoWindow iw; + iw.player = h->tempOwner; + if(message.size()) + iw.text << message; + else + iw.text.addTxt(MetaString::ADVOB_TXT, 16); + cb->showInfoDialog(&iw); + cb->startBattleI(h, this); + } + else + { + giveContentsUpToExp(h); + } +} diff --git a/lib/mapObjects/CGPandoraBox.h b/lib/mapObjects/CGPandoraBox.h new file mode 100644 index 000000000..227615cdf --- /dev/null +++ b/lib/mapObjects/CGPandoraBox.h @@ -0,0 +1,72 @@ +#pragma once + +#include "CObjectHandler.h" +#include "CArmedInstance.h" + +/* + * CGPandoraBox.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 + * + */ + +class DLL_LINKAGE CGPandoraBox : public CArmedInstance +{ +public: + std::string message; + bool hasGuardians; //helper - after battle even though we have no stacks, allows us to know that there was battle + + //gained things: + ui32 gainedExp; + si32 manaDiff; //amount of gained / lost mana + si32 moraleDiff; //morale modifier + si32 luckDiff; //luck modifier + TResources resources;//gained / lost resources + std::vector primskills;//gained / lost prim skills + std::vector abilities; //gained abilities + std::vector abilityLevels; //levels of gained abilities + std::vector artifacts; //gained artifacts + std::vector spells; //gained spells + CCreatureSet creatures; //gained creatures + + void initObj() override; + void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void heroLevelUpDone(const CGHeroInstance *hero) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & message & hasGuardians & gainedExp & manaDiff & moraleDiff & luckDiff & resources & primskills + & abilities & abilityLevels & artifacts & spells & creatures; + } +protected: + void giveContentsUpToExp(const CGHeroInstance *h) const; + void giveContentsAfterExp(const CGHeroInstance *h) const; +private: + void getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const; + void getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const; +}; + +class DLL_LINKAGE CGEvent : public CGPandoraBox //event objects +{ +public: + bool removeAfterVisit; //true if event is removed after occurring + ui8 availableFor; //players whom this event is available for + bool computerActivate; //true if computer player can activate this event + bool humanActivate; //true if human player can activate this event + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & removeAfterVisit & availableFor & computerActivate & humanActivate; + } + + void onHeroVisit(const CGHeroInstance * h) const override; +private: + void activated(const CGHeroInstance * h) const; +}; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp new file mode 100644 index 000000000..db903cbb9 --- /dev/null +++ b/lib/mapObjects/CGTownInstance.cpp @@ -0,0 +1,1165 @@ +/* + * CGTownInstance.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CGTownInstance.h" +#include "CObjectClassesHandler.h" + +#include "../NetPacks.h" +#include "../CGeneralTextHandler.h" + +using namespace boost::assign; + +std::vector CGTownInstance::merchantArtifacts; +std::vector CGTownInstance::universitySkills; + +void CGDwelling::initObj() +{ + switch(ID) + { + case Obj::CREATURE_GENERATOR1: + case Obj::CREATURE_GENERATOR4: + { + VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, cb->gameState()->getRandomGenerator()); + + if (getOwner() != PlayerColor::NEUTRAL) + cb->gameState()->players[getOwner()].dwellings.push_back (this); + } + //putStack(SlotID(0), new CStackInstance(CreatureID::GOLD_GOLEM, 9)); + //putStack(SlotID(1), new CStackInstance(CreatureID::DIAMOND_GOLEM, 6)); + + //putStack(SlotID(0), new CStackInstance(CreatureID::EARTH_ELEMENTAL, 12)); + break; + + case Obj::REFUGEE_CAMP: + //is handled within newturn func + break; + + case Obj::WAR_MACHINE_FACTORY: + creatures.resize(3); + creatures[0].second.push_back(CreatureID::BALLISTA); + creatures[1].second.push_back(CreatureID::FIRST_AID_TENT); + creatures[2].second.push_back(CreatureID::AMMO_CART); + break; + + default: + assert(0); + break; + } +} + +void CGDwelling::setProperty(ui8 what, ui32 val) +{ + switch (what) + { + case ObjProperty::OWNER: //change owner + if (ID == Obj::CREATURE_GENERATOR1) //single generators + { + if (tempOwner != PlayerColor::NEUTRAL) + { + std::vector >* dwellings = &cb->gameState()->players[tempOwner].dwellings; + dwellings->erase (std::find(dwellings->begin(), dwellings->end(), this)); + } + if (PlayerColor(val) != PlayerColor::NEUTRAL) //can new owner be neutral? + cb->gameState()->players[PlayerColor(val)].dwellings.push_back (this); + } + break; + case ObjProperty::AVAILABLE_CREATURE: + creatures.resize(1); + creatures[0].second.resize(1); + creatures[0].second[0] = CreatureID(val); + break; + } + CGObjectInstance::setProperty(what,val); +} +void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const +{ + if(ID == Obj::REFUGEE_CAMP && !creatures[0].first) //Refugee Camp, no available cres + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.text.addTxt(MetaString::ADVOB_TXT, 44); //{%s} \n\n The camp is deserted. Perhaps you should try next week. + iw.text.addReplacement(MetaString::OBJ_NAMES, ID); + cb->sendAndApply(&iw); + return; + } + + PlayerRelations::PlayerRelations relations = cb->gameState()->getPlayerRelations( h->tempOwner, tempOwner ); + + if ( relations == PlayerRelations::ALLIES ) + return;//do not allow recruiting or capturing + + if( !relations && stacksCount() > 0) //object is guarded, owned by enemy + { + BlockingDialog bd(true,false); + bd.player = h->tempOwner; + bd.text.addTxt(MetaString::GENERAL_TXT, 421); //Much to your dismay, the %s is guarded by %s %s. Do you wish to fight the guards? + bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); + bd.text.addReplacement(MetaString::ARRAY_TXT, 176 + Slots().begin()->second->getQuantityID()*3); + bd.text.addReplacement(*Slots().begin()->second); + cb->showBlockingDialog(&bd); + return; + } + + if(!relations && ID != Obj::WAR_MACHINE_FACTORY) + { + cb->setOwner(this, h->tempOwner); + } + + BlockingDialog bd (true,false); + bd.player = h->tempOwner; + if(ID == Obj::CREATURE_GENERATOR1 || ID == Obj::CREATURE_GENERATOR4) + { + bd.text.addTxt(MetaString::ADVOB_TXT, ID == Obj::CREATURE_GENERATOR1 ? 35 : 36); //{%s} Would you like to recruit %s? / {%s} Would you like to recruit %s, %s, %s, or %s? + bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); + for(auto & elem : creatures) + bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]); + } + else if(ID == Obj::REFUGEE_CAMP) + { + bd.text.addTxt(MetaString::ADVOB_TXT, 35); //{%s} Would you like to recruit %s? + bd.text.addReplacement(MetaString::OBJ_NAMES, ID); + for(auto & elem : creatures) + bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]); + } + else if(ID == Obj::WAR_MACHINE_FACTORY) + bd.text.addTxt(MetaString::ADVOB_TXT, 157); //{War Machine Factory} Would you like to purchase War Machines? + else + throw std::runtime_error("Illegal dwelling!"); + + cb->showBlockingDialog(&bd); +} + +void CGDwelling::newTurn() const +{ + if(cb->getDate(Date::DAY_OF_WEEK) != 1) //not first day of week + return; + + //town growths and War Machines Factories are handled separately + if(ID == Obj::TOWN || ID == Obj::WAR_MACHINE_FACTORY) + return; + + if(ID == Obj::REFUGEE_CAMP) //if it's a refugee camp, we need to pick an available creature + { + cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster(cb->gameState()->getRandomGenerator())); + } + + bool change = false; + + SetAvailableCreatures sac; + sac.creatures = creatures; + sac.tid = id; + for (size_t i = 0; i < creatures.size(); i++) + { + if(creatures[i].second.size()) + { + CCreature *cre = VLC->creh->creatures[creatures[i].second[0]]; + TQuantity amount = cre->growth * (1 + cre->valOfBonuses(Bonus::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(Bonus::CREATURE_GROWTH); + if (VLC->modh->settings.DWELLINGS_ACCUMULATE_CREATURES && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures + sac.creatures[i].first += amount; + else + sac.creatures[i].first = amount; + change = true; + } + } + + if(change) + cb->sendAndApply(&sac); +} + +void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const +{ + CreatureID crid = creatures[0].second[0]; + CCreature *crs = VLC->creh->creatures[crid]; + TQuantity count = creatures[0].first; + + if(crs->level == 1 && ID != Obj::REFUGEE_CAMP) //first level - creatures are for free + { + if(count) //there are available creatures + { + SlotID slot = h->getSlotFor(crid); + if(!slot.validSlot()) //no available slot + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.text.addTxt(MetaString::GENERAL_TXT, 425);//The %s would join your hero, but there aren't enough provisions to support them. + iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); + cb->showInfoDialog(&iw); + } + else //give creatures + { + SetAvailableCreatures sac; + sac.tid = id; + sac.creatures = creatures; + sac.creatures[0].first = 0; + + + InfoWindow iw; + iw.player = h->tempOwner; + iw.text.addTxt(MetaString::GENERAL_TXT, 423); //%d %s join your army. + iw.text.addReplacement(count); + iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); + + cb->showInfoDialog(&iw); + cb->sendAndApply(&sac); + cb->addToSlot(StackLocation(h, slot), crs, count); + } + } + else //there no creatures + { + InfoWindow iw; + iw.text.addTxt(MetaString::GENERAL_TXT, 422); //There are no %s here to recruit. + iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); + iw.player = h->tempOwner; + cb->sendAndApply(&iw); + } + } + else + { + if(ID == Obj::WAR_MACHINE_FACTORY) //pick available War Machines + { + //there is 1 war machine available to recruit if hero doesn't have one + SetAvailableCreatures sac; + sac.tid = id; + sac.creatures = creatures; + sac.creatures[0].first = !h->getArt(ArtifactPosition::MACH1); //ballista + sac.creatures[1].first = !h->getArt(ArtifactPosition::MACH3); //first aid tent + sac.creatures[2].first = !h->getArt(ArtifactPosition::MACH2); //ammo cart + cb->sendAndApply(&sac); + } + + OpenWindow ow; + ow.id1 = id.getNum(); + ow.id2 = h->id.getNum(); + ow.window = (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::REFUGEE_CAMP) + ? OpenWindow::RECRUITMENT_FIRST + : OpenWindow::RECRUITMENT_ALL; + cb->sendAndApply(&ow); + } +} + +void CGDwelling::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if (result.winner == 0) + { + onHeroVisit(hero); + } +} + +void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + auto relations = cb->getPlayerRelations(getOwner(), hero->getOwner()); + if(stacksCount() > 0 && relations == PlayerRelations::ENEMIES) //guards present + { + if(answer) + cb->startBattleI(hero, this); + } + else if(answer) + { + heroAcceptsCreatures(hero); + } +} + +int CGTownInstance::getSightRadious() const //returns sight distance +{ + if (subID == ETownType::TOWER) + { + if (hasBuilt(BuildingID::GRAIL)) //skyship + return -1; //entire map + if (hasBuilt(BuildingID::LOOKOUT_TOWER)) //lookout tower + return 20; + } + return 5; +} + +void CGTownInstance::setPropertyDer(ui8 what, ui32 val) +{ +///this is freakin' overcomplicated solution + switch (what) + { + case ObjProperty::STRUCTURE_ADD_VISITING_HERO: + bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, visitingHero->id.getNum()); + break; + case ObjProperty::STRUCTURE_CLEAR_VISITORS: + bonusingBuildings[val]->setProperty (ObjProperty::STRUCTURE_CLEAR_VISITORS, 0); + break; + case ObjProperty::STRUCTURE_ADD_GARRISONED_HERO: //add garrisoned hero to visitors + bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, garrisonHero->id.getNum()); + break; + case ObjProperty::BONUS_VALUE_FIRST: + bonusValue.first = val; + break; + case ObjProperty::BONUS_VALUE_SECOND: + bonusValue.second = val; + break; + } +} +CGTownInstance::EFortLevel CGTownInstance::fortLevel() const //0 - none, 1 - fort, 2 - citadel, 3 - castle +{ + if (hasBuilt(BuildingID::CASTLE)) + return CASTLE; + if (hasBuilt(BuildingID::CITADEL)) + return CITADEL; + if (hasBuilt(BuildingID::FORT)) + return FORT; + return NONE; +} + +int CGTownInstance::hallLevel() const // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol +{ + + if (hasBuilt(BuildingID::CAPITOL)) + return 3; + if (hasBuilt(BuildingID::CITY_HALL)) + return 2; + if (hasBuilt(BuildingID::TOWN_HALL)) + return 1; + if (hasBuilt(BuildingID::VILLAGE_HALL)) + return 0; + return -1; +} +int CGTownInstance::mageGuildLevel() const +{ + if (hasBuilt(BuildingID::MAGES_GUILD_5)) + return 5; + if (hasBuilt(BuildingID::MAGES_GUILD_4)) + return 4; + if (hasBuilt(BuildingID::MAGES_GUILD_3)) + return 3; + if (hasBuilt(BuildingID::MAGES_GUILD_2)) + return 2; + if (hasBuilt(BuildingID::MAGES_GUILD_1)) + return 1; + return 0; +} + +int CGTownInstance::getHordeLevel(const int & HID) const//HID - 0 or 1; returns creature level or -1 if that horde structure is not present +{ + return town->hordeLvl.at(HID); +} + +int CGTownInstance::creatureGrowth(const int & level) const +{ + return getGrowthInfo(level).totalGrowth(); +} + +GrowthInfo CGTownInstance::getGrowthInfo(int level) const +{ + GrowthInfo ret; + + if (level<0 || level >=GameConstants::CREATURES_PER_TOWN) + return ret; + if (creatures[level].second.empty()) + return ret; //no dwelling + + const CCreature *creature = VLC->creh->creatures[creatures[level].second.back()]; + const int base = creature->growth; + int castleBonus = 0; + + ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[590], base));// \n\nBasic growth %d" + + if (hasBuilt(BuildingID::CASTLE)) + ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::CASTLE, castleBonus = base)); + else if (hasBuilt(BuildingID::CITADEL)) + ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::CITADEL, castleBonus = base / 2)); + + if(town->hordeLvl.at(0) == level)//horde 1 + if(hasBuilt(BuildingID::HORDE_1)) + ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_1, creature->hordeGrowth)); + + if(town->hordeLvl.at(1) == level)//horde 2 + if(hasBuilt(BuildingID::HORDE_2)) + ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_2, creature->hordeGrowth)); + + int dwellingBonus = 0; + if(const PlayerState *p = cb->getPlayer(tempOwner, false)) + { + for(const CGDwelling *dwelling : p->dwellings) + if(vstd::contains(creatures[level].second, dwelling->creatures[0].second[0])) + dwellingBonus++; + } + + if(dwellingBonus) + ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[591], dwellingBonus));// \nExternal dwellings %+d + + //other *-of-legion-like bonuses (%d to growth cumulative with grail) + TBonusListPtr bonuses = getBonuses(Selector::type(Bonus::CREATURE_GROWTH).And(Selector::subtype(level))); + for(const Bonus *b : *bonuses) + ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val)); + + //statue-of-legion-like bonus: % to base+castle + TBonusListPtr bonuses2 = getBonuses(Selector::type(Bonus::CREATURE_GROWTH_PERCENT)); + for(const Bonus *b : *bonuses2) + ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val * (base + castleBonus) / 100)); + + if(hasBuilt(BuildingID::GRAIL)) //grail - +50% to ALL (so far added) growth + ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::GRAIL, ret.totalGrowth() / 2)); + + return ret; +} + +TResources CGTownInstance::dailyIncome() const +{ + TResources ret; + + for (auto & p : town->buildings) + { + BuildingID buildingUpgrade; + + for (auto & p2 : town->buildings) + { + if (p2.second->upgrade == p.first) + { + buildingUpgrade = p2.first; + } + } + + if (!hasBuilt(buildingUpgrade)&&(hasBuilt(p.first))) + { + ret += p.second->produce; + } + + } + + return ret; +} +bool CGTownInstance::hasFort() const +{ + return hasBuilt(BuildingID::FORT); +} +bool CGTownInstance::hasCapitol() const +{ + return hasBuilt(BuildingID::CAPITOL); +} +CGTownInstance::CGTownInstance() + :IShipyard(this), IMarket(this), town(nullptr), builded(0), destroyed(0), identifier(0), alignment(0xff) +{ + +} + +CGTownInstance::~CGTownInstance() +{ + for (auto & elem : bonusingBuildings) + delete elem; +} + +int CGTownInstance::spellsAtLevel(int level, bool checkGuild) const +{ + if(checkGuild && mageGuildLevel() < level) + return 0; + int ret = 6 - level; //how many spells are available at this level + + if (hasBuilt(BuildingID::LIBRARY, ETownType::TOWER)) + ret++; + + return ret; +} + +bool CGTownInstance::needsLastStack() const +{ + if(garrisonHero) + return true; + else return false; +} + +void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const +{ + if( !cb->gameState()->getPlayerRelations( getOwner(), h->getOwner() ))//if this is enemy + { + if(armedGarrison() || visitingHero) + { + const CGHeroInstance *defendingHero = nullptr; + const CArmedInstance *defendingArmy = this; + + if(visitingHero) + defendingHero = visitingHero; + else if(garrisonHero) + defendingHero = garrisonHero; + + if(defendingHero) + defendingArmy = defendingHero; + + bool outsideTown = (defendingHero == visitingHero && garrisonHero); + + //TODO + //"borrowing" army from garrison to visiting hero + + cb->startBattlePrimary(h, defendingArmy, getSightCenter(), h, defendingHero, false, (outsideTown ? nullptr : this)); + } + else + { + cb->setOwner(this, h->tempOwner); + removeCapitols(h->getOwner()); + cb->heroVisitCastle(this, h); + } + } + else if(h->visitablePos() == visitablePos()) + { + if (h->commander && !h->commander->alive) //rise commander. TODO: interactive script + { + SetCommanderProperty scp; + scp.heroid = h->id; + scp.which = SetCommanderProperty::ALIVE; + scp.amount = 1; + cb->sendAndApply (&scp); + } + cb->heroVisitCastle(this, h); + } + else + { + logGlobal->errorStream() << h->name << " visits allied town of " << name << " from different pos?"; + } +} + +void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const +{ + cb->stopHeroVisitCastle(this, h); +} + +void CGTownInstance::initObj() +///initialize town structures +{ + blockVisit = true; + hoverName = name + ", " + town->faction->name; + + if (subID == ETownType::DUNGEON) + creatures.resize(GameConstants::CREATURES_PER_TOWN+1);//extra dwelling for Dungeon + else + creatures.resize(GameConstants::CREATURES_PER_TOWN); + for (int level = 0; level < GameConstants::CREATURES_PER_TOWN; level++) + { + BuildingID buildID = BuildingID(BuildingID::DWELL_FIRST).advance(level); + int upgradeNum = 0; + + for (; town->buildings.count(buildID); upgradeNum++, buildID.advance(GameConstants::CREATURES_PER_TOWN)) + { + if (hasBuilt(buildID) && town->creatures.at(level).size() > upgradeNum) + creatures[level].second.push_back(town->creatures[level][upgradeNum]); + } + } + + switch (subID) + { //add new visitable objects + case 0: + bonusingBuildings.push_back (new COPWBonus(BuildingID::STABLES, this)); + break; + case 5: + bonusingBuildings.push_back (new COPWBonus(BuildingID::MANA_VORTEX, this)); + //fallthrough + case 2: case 3: case 6: + bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_4, this)); + break; + case 7: + bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_1, this)); + break; + } + //add special bonuses from buildings + + recreateBuildingsBonuses(); + updateAppearance(); +} + +void CGTownInstance::newTurn() const +{ + if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week + { + auto & rand = cb->gameState()->getRandomGenerator(); + + //give resources for Rampart, Mystic Pond + if (hasBuilt(BuildingID::MYSTIC_POND, ETownType::RAMPART) + && cb->getDate(Date::DAY) != 1 && (tempOwner < PlayerColor::PLAYER_LIMIT)) + { + int resID = rand.nextInt(2, 5); //bonus to random rare resource + resID = (resID==2)?1:resID; + int resVal = rand.nextInt(1, 4);//with size 1..4 + cb->giveResource(tempOwner, static_cast(resID), resVal); + cb->setObjProperty (id, ObjProperty::BONUS_VALUE_FIRST, resID); + cb->setObjProperty (id, ObjProperty::BONUS_VALUE_SECOND, resVal); + } + + if ( subID == ETownType::DUNGEON ) + for (auto & elem : bonusingBuildings) + { + if ((elem)->ID == BuildingID::MANA_VORTEX) + cb->setObjProperty (id, ObjProperty::STRUCTURE_CLEAR_VISITORS, (elem)->id); //reset visitors for Mana Vortex + } + + if (tempOwner == PlayerColor::NEUTRAL) //garrison growth for neutral towns + { + std::vector nativeCrits; //slots + for (auto & elem : Slots()) + { + if (elem.second->type->faction == subID) //native + { + nativeCrits.push_back(elem.first); //collect matching slots + } + } + if (nativeCrits.size()) + { + SlotID pos = *RandomGeneratorUtil::nextItem(nativeCrits, rand); + StackLocation sl(this, pos); + + const CCreature *c = getCreature(pos); + if (rand.nextInt(99) < 90 || c->upgrades.empty()) //increase number if no upgrade available + { + cb->changeStackCount(sl, c->growth); + } + else //upgrade + { + cb->changeStackType(sl, VLC->creh->creatures[*c->upgrades.begin()]); + } + } + if ((stacksCount() < GameConstants::ARMY_SIZE && rand.nextInt(99) < 25) || Slots().empty()) //add new stack + { + int i = rand.nextInt(std::min(GameConstants::CREATURES_PER_TOWN, cb->getDate(Date::MONTH) << 1) - 1); + if (!town->creatures[i].empty()) + { + CreatureID c = town->creatures[i][0]; + SlotID n; + + TQuantity count = creatureGrowth(i); + if (!count) // no dwelling + count = VLC->creh->creatures[c]->growth; + + {//no lower tiers or above current month + + if ((n = getSlotFor(c)).validSlot()) + { + StackLocation sl(this, n); + if (slotEmpty(n)) + cb->insertNewStack(sl, VLC->creh->creatures[c], count); + else //add to existing + cb->changeStackCount(sl, count); + } + } + } + } + } + } +} + +int3 CGTownInstance::getSightCenter() const +{ + return pos - int3(2,0,0); +} + +ui8 CGTownInstance::getPassableness() const +{ + if (!armedGarrison())//empty castle - anyone can visit + return GameConstants::ALL_PLAYERS; + if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit + return 0; + + ui8 mask = 0; + TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner); + for(PlayerColor it : ts->players) + mask |= 1< &offsets ) const +{ + offsets += int3(-1,2,0), int3(-3,2,0); +} + +void CGTownInstance::removeCapitols (PlayerColor owner) const +{ + if (hasCapitol()) // search if there's an older capitol + { + PlayerState* state = cb->gameState()->getPlayer (owner); //get all towns owned by player + for (auto i = state->towns.cbegin(); i < state->towns.cend(); ++i) + { + if (*i != this && (*i)->hasCapitol()) + { + RazeStructures rs; + rs.tid = id; + rs.bid.insert(BuildingID::CAPITOL); + rs.destroyed = destroyed; + cb->sendAndApply(&rs); + return; + } + } + } +} + +int CGTownInstance::getBoatType() const +{ + switch (town->faction->alignment) + { + case EAlignment::EVIL : return 0; + case EAlignment::GOOD : return 1; + case EAlignment::NEUTRAL : return 2; + } + assert(0); + return -1; +} + +int CGTownInstance::getMarketEfficiency() const +{ + if (!hasBuilt(BuildingID::MARKETPLACE)) + return 0; + + const PlayerState *p = cb->getPlayer(tempOwner); + assert(p); + + int marketCount = 0; + for(const CGTownInstance *t : p->towns) + if(t->hasBuilt(BuildingID::MARKETPLACE)) + marketCount++; + + return marketCount; +} + +bool CGTownInstance::allowsTrade(EMarketMode::EMarketMode mode) const +{ + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + case EMarketMode::RESOURCE_PLAYER: + return hasBuilt(BuildingID::MARKETPLACE); + + case EMarketMode::ARTIFACT_RESOURCE: + case EMarketMode::RESOURCE_ARTIFACT: + return hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::TOWER) + || hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::DUNGEON) + || hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::CONFLUX); + + case EMarketMode::CREATURE_RESOURCE: + return hasBuilt(BuildingID::FREELANCERS_GUILD, ETownType::STRONGHOLD); + + case EMarketMode::CREATURE_UNDEAD: + return hasBuilt(BuildingID::SKELETON_TRANSFORMER, ETownType::NECROPOLIS); + + case EMarketMode::RESOURCE_SKILL: + return hasBuilt(BuildingID::MAGIC_UNIVERSITY, ETownType::CONFLUX); + default: + assert(0); + return false; + } +} + +std::vector CGTownInstance::availableItemsIds(EMarketMode::EMarketMode mode) const +{ + if(mode == EMarketMode::RESOURCE_ARTIFACT) + { + std::vector ret; + for(const CArtifact *a : merchantArtifacts) + if(a) + ret.push_back(a->id); + else + ret.push_back(-1); + return ret; + } + else if ( mode == EMarketMode::RESOURCE_SKILL ) + { + return universitySkills; + } + else + return IMarket::availableItemsIds(mode); +} + +void CGTownInstance::setType(si32 ID, si32 subID) +{ + assert(ID == Obj::TOWN); // just in case + CGObjectInstance::setType(ID, subID); + town = VLC->townh->factions[subID]->town; + randomizeArmy(subID); + updateAppearance(); +} + +void CGTownInstance::updateAppearance() +{ + //FIXME: not the best way to do this + auto app = VLC->objtypeh->getHandlerFor(ID, subID)->getOverride(cb->gameState()->getTile(visitablePos())->terType, this); + if (app) + appearance = app.get(); +} + +std::string CGTownInstance::nodeName() const +{ + return "Town (" + (town ? town->faction->name : "unknown") + ") of " + name; +} + +void CGTownInstance::deserializationFix() +{ + attachTo(&townAndVis); + + //Hero is already handled by CGameState::attachArmedObjects + +// if(visitingHero) +// visitingHero->attachTo(&townAndVis); +// if(garrisonHero) +// garrisonHero->attachTo(this); +} + +void CGTownInstance::updateMoraleBonusFromArmy() +{ + Bonus *b = getBonusList().getFirst(Selector::sourceType(Bonus::ARMY).And(Selector::type(Bonus::MORALE))); + if(!b) + { + b = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1); + addNewBonus(b); + } + + if (garrisonHero) + b->val = 0; + else + CArmedInstance::updateMoraleBonusFromArmy(); +} + +void CGTownInstance::recreateBuildingsBonuses() +{ + static TPropagatorPtr playerProp(new CPropagatorNodeType(PLAYER)); + + BonusList bl; + getExportedBonusList().getBonuses(bl, Selector::sourceType(Bonus::TOWN_STRUCTURE)); + for(Bonus *b : bl) + removeBonus(b); + + //tricky! -> checks tavern only if no bratherhood of sword or not a castle + if(subID != ETownType::CASTLE || !addBonusIfBuilt(BuildingID::BROTHERHOOD, Bonus::MORALE, +2)) + addBonusIfBuilt(BuildingID::TAVERN, Bonus::MORALE, +1); + + if(subID == ETownType::CASTLE) //castle + { + addBonusIfBuilt(BuildingID::LIGHTHOUSE, Bonus::SEA_MOVEMENT, +500, playerProp); + addBonusIfBuilt(BuildingID::GRAIL, Bonus::MORALE, +2, playerProp); //colossus + } + else if(subID == ETownType::RAMPART) //rampart + { + addBonusIfBuilt(BuildingID::FOUNTAIN_OF_FORTUNE, Bonus::LUCK, +2); //fountain of fortune + addBonusIfBuilt(BuildingID::GRAIL, Bonus::LUCK, +2, playerProp); //guardian spirit + } + else if(subID == ETownType::TOWER) //tower + { + addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +15, PrimarySkill::KNOWLEDGE); //grail + } + else if(subID == ETownType::INFERNO) //Inferno + { + addBonusIfBuilt(BuildingID::STORMCLOUDS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER); //Brimstone Clouds + } + else if(subID == ETownType::NECROPOLIS) //necropolis + { + addBonusIfBuilt(BuildingID::COVER_OF_DARKNESS, Bonus::DARKNESS, +20); + addBonusIfBuilt(BuildingID::NECROMANCY_AMPLIFIER, Bonus::SECONDARY_SKILL_PREMY, +10, playerProp, SecondarySkill::NECROMANCY); //necromancy amplifier + addBonusIfBuilt(BuildingID::GRAIL, Bonus::SECONDARY_SKILL_PREMY, +20, playerProp, SecondarySkill::NECROMANCY); //Soul prison + } + else if(subID == ETownType::DUNGEON) //Dungeon + { + addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +12, PrimarySkill::SPELL_POWER); //grail + } + else if(subID == ETownType::STRONGHOLD) //Stronghold + { + addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +20, PrimarySkill::ATTACK); //grail + } + else if(subID == ETownType::FORTRESS) //Fortress + { + addBonusIfBuilt(BuildingID::GLYPHS_OF_FEAR, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE); //Glyphs of Fear + addBonusIfBuilt(BuildingID::BLOOD_OBELISK, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK); //Blood Obelisk + addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::ATTACK); //grail + addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::DEFENSE); //grail + } + else if(subID == ETownType::CONFLUX) + { + + } +} + +bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype /*= -1*/) +{ + static auto emptyPropagator = TPropagatorPtr(); + return addBonusIfBuilt(building, type, val, emptyPropagator, subtype); +} + +bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype /*= -1*/) +{ + if(hasBuilt(building)) + { + std::ostringstream descr; + descr << town->buildings.at(building)->Name() << " "; + if(val > 0) + descr << "+"; + else if(val < 0) + descr << "-"; + descr << val; + + Bonus *b = new Bonus(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, descr.str(), subtype); + if(prop) + b->addPropagator(prop); + addNewBonus(b); + return true; + } + + return false; +} + +void CGTownInstance::setVisitingHero(CGHeroInstance *h) +{ + assert(!!visitingHero == !h); + if(h) + { + PlayerState *p = cb->gameState()->getPlayer(h->tempOwner); + assert(p); + h->detachFrom(p); + h->attachTo(&townAndVis); + visitingHero = h; + h->visitedTown = this; + h->inTownGarrison = false; + } + else + { + PlayerState *p = cb->gameState()->getPlayer(visitingHero->tempOwner); + visitingHero->visitedTown = nullptr; + visitingHero->detachFrom(&townAndVis); + visitingHero->attachTo(p); + visitingHero = nullptr; + } +} + +void CGTownInstance::setGarrisonedHero(CGHeroInstance *h) +{ + assert(!!garrisonHero == !h); + if(h) + { + PlayerState *p = cb->gameState()->getPlayer(h->tempOwner); + assert(p); + h->detachFrom(p); + h->attachTo(this); + garrisonHero = h; + h->visitedTown = this; + h->inTownGarrison = true; + } + else + { + PlayerState *p = cb->gameState()->getPlayer(garrisonHero->tempOwner); + garrisonHero->visitedTown = nullptr; + garrisonHero->inTownGarrison = false; + garrisonHero->detachFrom(this); + garrisonHero->attachTo(p); + garrisonHero = nullptr; + } + updateMoraleBonusFromArmy(); //avoid giving morale bonus for same army twice +} + +bool CGTownInstance::armedGarrison() const +{ + return stacksCount() || garrisonHero; +} + +int CGTownInstance::getTownLevel() const +{ + // count all buildings that are not upgrades + return boost::range::count_if(builtBuildings, [&](const BuildingID & build) + { + return town->buildings.at(build) && town->buildings.at(build)->upgrade == -1; + }); +} + +CBonusSystemNode * CGTownInstance::whatShouldBeAttached() +{ + return &townAndVis; +} + +const CArmedInstance * CGTownInstance::getUpperArmy() const +{ + if(garrisonHero) + return garrisonHero; + return this; +} + +bool CGTownInstance::hasBuilt(BuildingID buildingID, int townID) const +{ + if (townID == town->faction->index || townID == ETownType::ANY) + return hasBuilt(buildingID); + return false; +} + +bool CGTownInstance::hasBuilt(BuildingID buildingID) const +{ + return vstd::contains(builtBuildings, buildingID); +} + +void CGTownInstance::addHeroToStructureVisitors( const CGHeroInstance *h, si32 structureInstanceID ) const +{ + if(visitingHero == h) + cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_VISITING_HERO, structureInstanceID); //add to visitors + else if(garrisonHero == h) + cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_GARRISONED_HERO, structureInstanceID); //then it must be garrisoned hero + else + { + //should never ever happen + logGlobal->errorStream() << "Cannot add hero " << h->name << " to visitors of structure #" << structureInstanceID; + assert(0); + } +} + +void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner == 0) + { + removeCapitols(hero->getOwner()); + cb->setOwner (this, hero->tempOwner); //give control after checkout is done + FoWChange fw; + fw.player = hero->tempOwner; + fw.mode = 1; + getSightTiles (fw.tiles); //update visibility for castle structures + cb->sendAndApply (&fw); + } +} + +COPWBonus::COPWBonus (BuildingID index, CGTownInstance *TOWN) +{ + ID = index; + town = TOWN; + id = town->bonusingBuildings.size(); +} +void COPWBonus::setProperty(ui8 what, ui32 val) +{ + switch (what) + { + case ObjProperty::VISITORS: + visitors.insert(val); + break; + case ObjProperty::STRUCTURE_CLEAR_VISITORS: + visitors.clear(); + break; + } +} +void COPWBonus::onHeroVisit (const CGHeroInstance * h) const +{ + ObjectInstanceID heroID = h->id; + if (town->hasBuilt(ID)) + { + InfoWindow iw; + iw.player = h->tempOwner; + switch (town->subID) + { + case ETownType::CASTLE: //Stables + if (!h->hasBonusFrom(Bonus::OBJECT, Obj::STABLES)) //does not stack with advMap Stables + { + GiveBonus gb; + gb.bonus = Bonus(Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, Bonus::OBJECT, 600, 94, VLC->generaltexth->arraytxt[100]); + gb.id = heroID.getNum(); + cb->giveHeroBonus(&gb); + iw.text << VLC->generaltexth->allTexts[580]; + cb->showInfoDialog(&iw); + } + break; + case ETownType::DUNGEON: //Mana Vortex + if (visitors.empty() && h->mana <= h->manaLimit() * 2) + { + cb->setManaPoints (heroID, 2 * h->manaLimit()); + //TODO: investigate line below + //cb->setObjProperty (town->id, ObjProperty::VISITED, true); + iw.text << VLC->generaltexth->allTexts[579]; + cb->showInfoDialog(&iw); + town->addHeroToStructureVisitors(h, id); + } + break; + } + } +} +CTownBonus::CTownBonus (BuildingID index, CGTownInstance *TOWN) +{ + ID = index; + town = TOWN; + id = town->bonusingBuildings.size(); +} +void CTownBonus::setProperty (ui8 what, ui32 val) +{ + if(what == ObjProperty::VISITORS) + visitors.insert(ObjectInstanceID(val)); +} +void CTownBonus::onHeroVisit (const CGHeroInstance * h) const +{ + ObjectInstanceID heroID = h->id; + if (town->hasBuilt(ID) && visitors.find(heroID) == visitors.end()) + { + InfoWindow iw; + PrimarySkill::PrimarySkill what = PrimarySkill::ATTACK; + int val=0, mid=0; + switch (ID) + { + case BuildingID::SPECIAL_4: + switch(town->subID) + { + case ETownType::TOWER: //wall + what = PrimarySkill::KNOWLEDGE; + val = 1; + mid = 581; + iw.components.push_back (Component(Component::PRIM_SKILL, 3, 1, 0)); + break; + case ETownType::INFERNO: //order of fire + what = PrimarySkill::SPELL_POWER; + val = 1; + mid = 582; + iw.components.push_back (Component(Component::PRIM_SKILL, 2, 1, 0)); + break; + case ETownType::STRONGHOLD://hall of Valhalla + what = PrimarySkill::ATTACK; + val = 1; + mid = 584; + iw.components.push_back (Component(Component::PRIM_SKILL, 0, 1, 0)); + break; + case ETownType::DUNGEON://academy of battle scholars + what = PrimarySkill::EXPERIENCE; + val = h->calculateXp(1000); + mid = 583; + iw.components.push_back (Component(Component::EXPERIENCE, 0, val, 0)); + break; + } + break; + case BuildingID::SPECIAL_1: + switch(town->subID) + { + case ETownType::FORTRESS: //cage of warlords + what = PrimarySkill::DEFENSE; + val = 1; + mid = 585; + iw.components.push_back (Component(Component::PRIM_SKILL, 1, 1, 0)); + break; + } + break; + } + assert(mid); + iw.player = cb->getOwner(heroID); + iw.text << VLC->generaltexth->allTexts[mid]; + cb->showInfoDialog(&iw); + cb->changePrimSkill (cb->getHero(heroID), what, val); + town->addHeroToStructureVisitors(h, id); + } +} + +GrowthInfo::Entry::Entry(const std::string &format, int _count) + : count(_count) +{ + description = boost::str(boost::format(format) % count); +} + +GrowthInfo::Entry::Entry(int subID, BuildingID building, int _count) + : count(_count) +{ + description = boost::str(boost::format("%s %+d") % VLC->townh->factions[subID]->town->buildings.at(building)->Name() % count); +} + +CTownAndVisitingHero::CTownAndVisitingHero() +{ + setNodeType(TOWN_AND_VISITOR); +} + +int GrowthInfo::totalGrowth() const +{ + int ret = 0; + for(const Entry &entry : entries) + ret += entry.count; + + return ret; +} diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h new file mode 100644 index 000000000..fda070a30 --- /dev/null +++ b/lib/mapObjects/CGTownInstance.h @@ -0,0 +1,248 @@ +#pragma once + +#include "CObjectHandler.h" +#include "CGMarket.h" // For IMarket interface +#include "CArmedInstance.h" + +#include "../CTownHandler.h" // For CTown + +/* + * CGTownInstance.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 + * + */ + +class CCastleEvent; + +class DLL_LINKAGE CSpecObjInfo +{ +public: + virtual ~CSpecObjInfo(){}; + PlayerColor player; //owner +}; + +class DLL_LINKAGE CCreGenAsCastleInfo : public virtual CSpecObjInfo +{ +public: + bool asCastle; + ui32 identifier; + ui8 castles[2]; //allowed castles +}; + +class DLL_LINKAGE CCreGenLeveledInfo : public virtual CSpecObjInfo +{ +public: + ui8 minLevel, maxLevel; //minimal and maximal level of creature in dwelling: <0, 6> +}; + +class DLL_LINKAGE CCreGenLeveledCastleInfo : public CCreGenAsCastleInfo, public CCreGenLeveledInfo +{ +}; + +class DLL_LINKAGE CGDwelling : public CArmedInstance +{ +public: + typedef std::vector > > TCreaturesSet; + + CSpecObjInfo * info; //h3m info about dewlling + TCreaturesSet creatures; //creatures[level] -> + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this) & creatures; + } + + void initObj() override; + void onHeroVisit(const CGHeroInstance * h) const override; + void newTurn() const override; + void setProperty(ui8 what, ui32 val) override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + +private: + void heroAcceptsCreatures(const CGHeroInstance *h) const; +}; + +class DLL_LINKAGE CGTownBuilding : public IObjectInterface +{ +///basic class for town structures handled as map objects +public: + BuildingID ID; //from buildig list + si32 id; //identifies its index on towns vector + CGTownInstance *town; + + template void serialize(Handler &h, const int version) + { + h & ID & id; + } +}; +class DLL_LINKAGE COPWBonus : public CGTownBuilding +{///used for OPW bonusing structures +public: + std::set visitors; + void setProperty(ui8 what, ui32 val) override; + void onHeroVisit (const CGHeroInstance * h) const override; + + COPWBonus (BuildingID index, CGTownInstance *TOWN); + COPWBonus (){ID = BuildingID::NONE; town = nullptr;}; + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & visitors; + } +}; + +class DLL_LINKAGE CTownBonus : public CGTownBuilding +{ +///used for one-time bonusing structures +///feel free to merge inheritance tree +public: + std::set visitors; + void setProperty(ui8 what, ui32 val) override; + void onHeroVisit (const CGHeroInstance * h) const override; + + CTownBonus (BuildingID index, CGTownInstance *TOWN); + CTownBonus (){ID = BuildingID::NONE; town = nullptr;}; + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & visitors; + } +}; + +class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode +{ +public: + CTownAndVisitingHero(); +}; + +struct DLL_LINKAGE GrowthInfo +{ + struct Entry + { + int count; + std::string description; + Entry(const std::string &format, int _count); + Entry(int subID, BuildingID building, int _count); + }; + + std::vector entries; + int totalGrowth() const; +}; + +class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket +{ +public: + enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3}; + + CTownAndVisitingHero townAndVis; + const CTown * town; + std::string name; // name of town + si32 builded; //how many buildings has been built this turn + si32 destroyed; //how many buildings has been destroyed this turn + ConstTransitivePtr garrisonHero, visitingHero; + ui32 identifier; //special identifier from h3m (only > RoE maps) + si32 alignment; + std::set forbiddenBuildings, builtBuildings; + std::vector bonusingBuildings; + std::vector possibleSpells, obligatorySpells; + std::vector > spells; //spells[level] -> vector of spells, first will be available in guild + std::list events; + std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond); + + ////////////////////////////////////////////////////////////////////////// + static std::vector merchantArtifacts; //vector of artifacts available at Artifact merchant, NULLs possible (for making empty space when artifact is bought) + static std::vector universitySkills;//skills for university of magic + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + h & static_cast(*this); + h & name & builded & destroyed & identifier; + h & garrisonHero & visitingHero; + h & alignment & forbiddenBuildings & builtBuildings & bonusValue + & possibleSpells & obligatorySpells & spells & /*strInfo & */events & bonusingBuildings; + + for (std::vector::iterator i = bonusingBuildings.begin(); i!=bonusingBuildings.end(); i++) + (*i)->town = this; + + h & town & townAndVis; + BONUS_TREE_DESERIALIZATION_FIX + + vstd::erase_if(builtBuildings, [this](BuildingID building) -> bool + { + if(!town->buildings.count(building) || !town->buildings.at(building)) + { + logGlobal->errorStream() << boost::format("#1444-like issue in CGTownInstance::serialize. From town %s at %s removing the bogus builtBuildings item %s") + % name % pos % building; + return true; + } + return false; + }); + } + ////////////////////////////////////////////////////////////////////////// + + CBonusSystemNode *whatShouldBeAttached() override; + std::string nodeName() const override; + void updateMoraleBonusFromArmy() override; + void deserializationFix(); + void recreateBuildingsBonuses(); + bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr &prop, int subtype = -1); //returns true if building is built and bonus has been added + bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype = -1); //convienence version of above + void setVisitingHero(CGHeroInstance *h); + void setGarrisonedHero(CGHeroInstance *h); + const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself + + ////////////////////////////////////////////////////////////////////////// + + ui8 getPassableness() const; //bitmap - if the bit is set the corresponding player can pass through the visitable tiles of object, even if it's blockvis; if not set - default properties from definfo are used + int3 getSightCenter() const override; //"center" tile from which the sight distance is calculated + int getSightRadious() const override; //returns sight distance + int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral + void getOutOffsets(std::vector &offsets) const; //offsets to obj pos when we boat can be placed + int getMarketEfficiency() const override; //=market count + bool allowsTrade(EMarketMode::EMarketMode mode) const; + std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; + + void setType(si32 ID, si32 subID); + void updateAppearance(); + + ////////////////////////////////////////////////////////////////////////// + + bool needsLastStack() const; + CGTownInstance::EFortLevel fortLevel() const; + int hallLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol + int mageGuildLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol + int getHordeLevel(const int & HID) const; //HID - 0 or 1; returns creature level or -1 if that horde structure is not present + int creatureGrowth(const int & level) const; + GrowthInfo getGrowthInfo(int level) const; + bool hasFort() const; + bool hasCapitol() const; + //checks if building is constructed and town has same subID + bool hasBuilt(BuildingID buildingID) const; + bool hasBuilt(BuildingID buildingID, int townID) const; + TResources dailyIncome() const; //calculates daily income of this town + int spellsAtLevel(int level, bool checkGuild) const; //levels are counted from 1 (1 - 5) + bool armedGarrison() const; //true if town has creatures in garrison or garrisoned hero + int getTownLevel() const; + + void removeCapitols (PlayerColor owner) const; + void addHeroToStructureVisitors(const CGHeroInstance *h, si32 structureInstanceID) const; //hero must be visiting or garrisoned in town + + CGTownInstance(); + virtual ~CGTownInstance(); + + ///IObjectInterface overrides + void newTurn() const override; + void onHeroVisit(const CGHeroInstance * h) const override; + void onHeroLeave(const CGHeroInstance * h) const override; + void initObj() override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; +protected: + void setPropertyDer(ui8 what, ui32 val) override; +}; diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp new file mode 100644 index 000000000..122068eb6 --- /dev/null +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -0,0 +1,397 @@ +#include "StdInc.h" +#include "CObjectClassesHandler.h" + +#include "../filesystem/Filesystem.h" +#include "../filesystem/CBinaryReader.h" +#include "../lib/VCMI_Lib.h" +#include "../GameConstants.h" +#include "../StringConstants.h" +#include "../CGeneralTextHandler.h" +#include "../CModHandler.h" +#include "../JsonNode.h" + +#include "CRewardableConstructor.h" +#include "CommonConstructors.h" +#include "MapObjects.h" + +/* + * CObjectClassesHandler.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +CObjectClassesHandler::CObjectClassesHandler() +{ +#define SET_HANDLER_CLASS(STRING, CLASSNAME) handlerConstructors[STRING] = std::make_shared; +#define SET_HANDLER(STRING, TYPENAME) handlerConstructors[STRING] = std::make_shared > + + // list of all known handlers, hardcoded for now since the only way to add new objects is via C++ code + //Note: should be in sync with registerTypesMapObjectTypes function + SET_HANDLER_CLASS("configurable", CRewardableConstructor); + SET_HANDLER_CLASS("dwelling", CDwellingInstanceConstructor); + SET_HANDLER_CLASS("hero", CHeroInstanceConstructor); + SET_HANDLER_CLASS("town", CTownInstanceConstructor); + SET_HANDLER_CLASS("bank", CBankInstanceConstructor); + + SET_HANDLER_CLASS("static", CObstacleConstructor); + SET_HANDLER_CLASS("", CObstacleConstructor); + + SET_HANDLER("generic", CGObjectInstance); + SET_HANDLER("market", CGMarket); + SET_HANDLER("cartographer", CCartographer); + SET_HANDLER("artifact", CGArtifact); + SET_HANDLER("blackMarket", CGBlackMarket); + SET_HANDLER("boat", CGBoat); + SET_HANDLER("bonusingObject", CGBonusingObject); + SET_HANDLER("borderGate", CGBorderGate); + SET_HANDLER("borderGuard", CGBorderGuard); + SET_HANDLER("monster", CGCreature); + SET_HANDLER("denOfThieves", CGDenOfthieves); + SET_HANDLER("event", CGEvent); + SET_HANDLER("garrison", CGGarrison); + SET_HANDLER("heroPlaceholder", CGHeroPlaceholder); + SET_HANDLER("keymaster", CGKeymasterTent); + SET_HANDLER("lighthouse", CGLighthouse); + SET_HANDLER("magi", CGMagi); + SET_HANDLER("magicSpring", CGMagicSpring); + SET_HANDLER("magicWell", CGMagicWell); + SET_HANDLER("market", CGMarket); + SET_HANDLER("mine", CGMine); + SET_HANDLER("obelisk", CGObelisk); + SET_HANDLER("observatory", CGObservatory); + SET_HANDLER("onceVisitable", CGOnceVisitable); + SET_HANDLER("pandora", CGPandoraBox); + SET_HANDLER("pickable", CGPickable); + SET_HANDLER("prison", CGHeroInstance); + SET_HANDLER("questGuard", CGQuestGuard); + SET_HANDLER("resource", CGResource); + SET_HANDLER("scholar", CGScholar); + SET_HANDLER("seerHut", CGSeerHut); + SET_HANDLER("shipyard", CGShipyard); + SET_HANDLER("shrine", CGShrine); + SET_HANDLER("sign", CGSignBottle); + SET_HANDLER("siren", CGSirens); + SET_HANDLER("teleport", CGTeleport); + SET_HANDLER("university", CGUniversity); + SET_HANDLER("oncePerHero", CGVisitableOPH); + SET_HANDLER("oncePerWeek", CGVisitableOPW); + SET_HANDLER("witch", CGWitchHut); + +#undef SET_HANDLER_CLASS +#undef SET_HANDLER +} + +std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) +{ + CLegacyConfigParser parser("Data/Objects.txt"); + size_t totalNumber = parser.readNumber(); // first line contains number of objects to read and nothing else + parser.endLine(); + + for (size_t i=0; i key(templ.id.num, templ.subid); + legacyTemplates.insert(std::make_pair(key, templ)); + } + + std::vector ret(dataSize);// create storage for 256 objects + assert(dataSize == 256); + + CLegacyConfigParser namesParser("Data/ObjNames.txt"); + for (size_t i=0; i<256; i++) + { + ret[i]["name"].String() = namesParser.readString(); + namesParser.endLine(); + } + return ret; +} + +/// selects preferred ID (or subID) for new object +template +si32 selectNextID(const JsonNode & fixedID, const Map & map, si32 defaultID) +{ + if (!fixedID.isNull() && fixedID.Float() < defaultID) + return fixedID.Float(); // H3M object with fixed ID + + if (map.empty()) + return defaultID; // no objects loaded, keep gap for H3M objects + if (map.rbegin()->first > defaultID) + return map.rbegin()->first + 1; // some modded objects loaded, return next available + + return defaultID; // some H3M objects loaded, first modded found +} + +void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContainter * obj) +{ + if (!handlerConstructors.count(obj->handlerName)) + { + logGlobal->errorStream() << "Handler with name " << obj->handlerName << " was not found!"; + return; + } + auto handler = handlerConstructors.at(obj->handlerName)(); + handler->init(entry); + + si32 id = selectNextID(entry["index"], obj->objects, 1000); + handler->setType(obj->id, id); + + if (handler->getTemplates().empty()) + { + auto range = legacyTemplates.equal_range(std::make_pair(obj->id, id)); + for (auto & templ : boost::make_iterator_range(range.first, range.second)) + { + handler->addTemplate(templ.second); + } + legacyTemplates.erase(range.first, range.second); + } + + obj->objects[id] = handler; + logGlobal->debugStream() << "Loaded object " << obj->id << ":" << id; +} + +CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const JsonNode & json) +{ + auto obj = new ObjectContainter(); + obj->name = json["name"].String(); + obj->handlerName = json["handler"].String(); + obj->base = json["base"]; + obj->id = selectNextID(json["index"], objects, 256); + for (auto entry : json["types"].Struct()) + { + loadObjectEntry(entry.second, obj); + } + return obj; +} + +void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data) +{ + auto object = loadFromJson(data); + objects[object->id] = object; + + VLC->modh->identifiers.registerObject(scope, "object", name, object->id); +} + +void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) +{ + auto object = loadFromJson(data); + + assert(objects[index] == nullptr); // ensure that this id was not loaded before + objects[index] = object; + + VLC->modh->identifiers.registerObject(scope, "object", name, object->id); +} + +void CObjectClassesHandler::loadSubObject(std::string name, JsonNode config, si32 ID, boost::optional subID) +{ + config.setType(JsonNode::DATA_STRUCT); // ensure that input is not NULL + assert(objects.count(ID)); + if (subID) + { + assert(objects.at(ID)->objects.count(subID.get()) == 0); + assert(config["index"].isNull()); + config["index"].Float() = subID.get(); + } + + std::string oldMeta = config.meta; // FIXME: move into inheritNode? + JsonUtils::inherit(config, objects.at(ID)->base); + config.setMeta(oldMeta); + loadObjectEntry(config, objects[ID]); +} + +void CObjectClassesHandler::removeSubObject(si32 ID, si32 subID) +{ + assert(objects.count(ID)); + assert(objects.at(ID)->objects.count(subID)); + objects.at(ID)->objects.erase(subID); +} + +std::vector CObjectClassesHandler::getDefaultAllowed() const +{ + return std::vector(); //TODO? +} + +TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype) const +{ + if (objects.count(type)) + { + if (objects.at(type)->objects.count(subtype)) + return objects.at(type)->objects.at(subtype); + } + logGlobal->errorStream() << "Failed to find object of type " << type << ":" << subtype; + assert(0); // FIXME: throw error? + return nullptr; +} + +std::set CObjectClassesHandler::knownObjects() const +{ + std::set ret; + + for (auto entry : objects) + ret.insert(entry.first); + + return ret; +} + +std::set CObjectClassesHandler::knownSubObjects(si32 primaryID) const +{ + std::set ret; + + if (objects.count(primaryID)) + { + for (auto entry : objects.at(primaryID)->objects) + ret.insert(entry.first); + } + return ret; +} + +void CObjectClassesHandler::beforeValidate(JsonNode & object) +{ + for (auto & entry : object["types"].Struct()) + { + JsonUtils::inherit(entry.second, object["base"]); + for (auto & templ : entry.second["templates"].Struct()) + { + JsonUtils::inherit(templ.second, entry.second["base"]); + } + } +} + +void CObjectClassesHandler::afterLoadFinalization() +{ + for (auto entry : objects) + { + for (auto obj : entry.second->objects) + { + obj.second->afterLoadFinalization(); + if (obj.second->getTemplates().empty()) + logGlobal->warnStream() << "No templates found for " << entry.first << ":" << obj.first; + } + } +} + +std::string CObjectClassesHandler::getObjectName(si32 type) const +{ + assert(objects.count(type)); + return objects.at(type)->name; +} + +void AObjectTypeHandler::setType(si32 type, si32 subtype) +{ + this->type = type; + this->subtype = subtype; +} + +static ui32 loadJsonOrMax(const JsonNode & input) +{ + if (input.isNull()) + return std::numeric_limits::max(); + else + return input.Float(); +} + +void AObjectTypeHandler::init(const JsonNode & input) +{ + base = input["base"]; + + if (!input["rmg"].isNull()) + { + rmgInfo.value = input["rmg"]["value"].Float(); + rmgInfo.mapLimit = loadJsonOrMax(input["rmg"]["mapLimit"]); + rmgInfo.zoneLimit = loadJsonOrMax(input["rmg"]["zoneLimit"]); + rmgInfo.rarity = input["rmg"]["rarity"].Float(); + } // else block is not needed - set in constructor + + for (auto entry : input["templates"].Struct()) + { + entry.second.setType(JsonNode::DATA_STRUCT); + JsonUtils::inherit(entry.second, base); + + ObjectTemplate tmpl; + tmpl.id = Obj(type); + tmpl.subid = subtype; + tmpl.stringID = entry.first; // FIXME: create "fullID" - type.object.template? + tmpl.readJson(entry.second); + templates.push_back(tmpl); + } + initTypeData(input); +} + +bool AObjectTypeHandler::objectFilter(const CGObjectInstance *, const ObjectTemplate &) const +{ + return false; // by default there are no overrides +} + +void AObjectTypeHandler::initTypeData(const JsonNode & input) +{ +} + +void AObjectTypeHandler::addTemplate(ObjectTemplate templ) +{ + templ.id = Obj(type); + templ.subid = subtype; + templates.push_back(templ); +} + +void AObjectTypeHandler::addTemplate(JsonNode config) +{ + config.setType(JsonNode::DATA_STRUCT); // ensure that input is not null + JsonUtils::inherit(config, base); + ObjectTemplate tmpl; + tmpl.id = Obj(type); + tmpl.subid = subtype; + tmpl.stringID = ""; // TODO? + tmpl.readJson(config); + addTemplate(tmpl); +} + +std::vector AObjectTypeHandler::getTemplates() const +{ + return templates; +} + +std::vector AObjectTypeHandler::getTemplates(si32 terrainType) const// FIXME: replace with ETerrainType +{ + std::vector templates = getTemplates(); + std::vector filtered; + + std::copy_if(templates.begin(), templates.end(), std::back_inserter(filtered), [&](const ObjectTemplate & obj) + { + return obj.canBePlacedAt(ETerrainType(terrainType)); + }); + // H3 defines allowed terrains in a weird way - artifacts, monsters and resources have faulty masks here + // Perhaps we should re-define faulty templates and remove this workaround (already done for resources) + if (type == Obj::ARTIFACT || type == Obj::MONSTER) + return templates; + else + return filtered; +} + +boost::optional AObjectTypeHandler::getOverride(si32 terrainType, const CGObjectInstance * object) const +{ + std::vector ret = getTemplates(terrainType); + for (auto & tmpl : ret) + { + if (objectFilter(object, tmpl)) + return tmpl; + } + return boost::optional(); +} + +const RandomMapInfo & AObjectTypeHandler::getRMGInfo() +{ + return rmgInfo; +} + +bool AObjectTypeHandler::isStaticObject() +{ + return false; // most of classes are not static +} + +void AObjectTypeHandler::afterLoadFinalization() +{ +} diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h new file mode 100644 index 000000000..95b32fc49 --- /dev/null +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -0,0 +1,218 @@ +#pragma once + +#include "ObjectTemplate.h" + +#include "../GameConstants.h" +#include "../ConstTransitivePtr.h" +#include "../IHandlerBase.h" + +/* + * CObjectClassesHandler.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 + * + */ + +class JsonNode; +class CRandomGenerator; + +/// Structure that describes placement rules for this object in random map +struct RandomMapInfo +{ + /// How valuable this object is, 1k = worthless, 10k = Utopia-level + ui32 value; + + /// How many of such objects can be placed on map, 0 = object can not be placed by RMG + ui32 mapLimit; + + /// How many of such objects can be placed in one zone, 0 = unplaceable + ui32 zoneLimit; + + /// Rarity of object, 5 = extremely rare, 100 = common + ui32 rarity; + + RandomMapInfo(): + value(0), + mapLimit(0), + zoneLimit(0), + rarity(0) + {} + + template void serialize(Handler &h, const int version) + { + h & value & mapLimit & zoneLimit & rarity; + } +}; + +class IObjectInfo +{ +public: + struct CArmyStructure + { + ui32 totalStrength; + ui32 shootersStrength; + ui32 flyersStrength; + ui32 walkersStrength; + + CArmyStructure() : + totalStrength(0), + shootersStrength(0), + flyersStrength(0), + walkersStrength(0) + {} + + bool operator <(const CArmyStructure & other) + { + return this->totalStrength < other.totalStrength; + } + }; + + /// Returns possible composition of guards. Actual guards would be + /// somewhere between these two values + virtual CArmyStructure minGuards() const { return CArmyStructure(); } + virtual CArmyStructure maxGuards() const { return CArmyStructure(); } + + virtual bool givesResources() const { return false; } + + virtual bool givesExperience() const { return false; } + virtual bool givesMana() const { return false; } + virtual bool givesMovement() const { return false; } + + virtual bool givesPrimarySkills() const { return false; } + virtual bool givesSecondarySkills() const { return false; } + + virtual bool givesArtifacts() const { return false; } + virtual bool givesCreatures() const { return false; } + virtual bool givesSpells() const { return false; } + + virtual bool givesBonuses() const { return false; } +}; + +class CGObjectInstance; + +class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable +{ + RandomMapInfo rmgInfo; + + si32 type; + si32 subtype; + + JsonNode base; /// describes base template + + std::vector templates; +protected: + + virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; + + /// initialization for classes that inherit this one + virtual void initTypeData(const JsonNode & input); +public: + virtual ~AObjectTypeHandler(){} + + void setType(si32 type, si32 subtype); + + /// loads generic data from Json structure and passes it towards type-specific constructors + void init(const JsonNode & input); + + void addTemplate(ObjectTemplate templ); + void addTemplate(JsonNode config); + + /// returns all templates matching parameters + std::vector getTemplates() const; + std::vector getTemplates(si32 terrainType) const; + + /// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle) + /// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server) + boost::optional getOverride(si32 terrainType, const CGObjectInstance * object) const; + + const RandomMapInfo & getRMGInfo(); + + virtual bool isStaticObject(); + + virtual void afterLoadFinalization(); + + /// Creates object and set up core properties (like ID/subID). Object is NOT initialized + /// to allow creating objects before game start (e.g. map loading) + virtual CGObjectInstance * create(ObjectTemplate tmpl) const = 0; + + /// Configures object properties. Should be re-entrable, resetting state of the object if necessarily + /// This should set remaining properties, including randomized or depending on map + virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const = 0; + + /// Returns object configuration, if available. Othervice returns NULL + virtual std::unique_ptr getObjectInfo(ObjectTemplate tmpl) const = 0; + + template void serialize(Handler &h, const int version) + { + h & type & subtype & templates & rmgInfo; + } +}; + +typedef std::shared_ptr TObjectTypeHandler; + +class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase +{ + /// Small internal structure that contains information on specific group of objects + /// (creating separate entity is overcomplicating at least at this point) + struct ObjectContainter + { + si32 id; + + std::string name; // human-readable name + std::string handlerName; // ID of handler that controls this object, shoul be determined using hadlerConstructor map + + JsonNode base; + std::map objects; + + template void serialize(Handler &h, const int version) + { + h & name & handlerName & base & objects; + } + }; + + typedef std::multimap, ObjectTemplate> TTemplatesContainer; + + /// list of object handlers, each of them handles only one type + std::map objects; + + /// map that is filled during contruction with all known handlers. Not serializeable due to usage of std::function + std::map > handlerConstructors; + + /// container with H3 templates, used only during loading, no need to serialize it + TTemplatesContainer legacyTemplates; + + void loadObjectEntry(const JsonNode & entry, ObjectContainter * obj); + ObjectContainter * loadFromJson(const JsonNode & json); +public: + CObjectClassesHandler(); + + std::vector loadLegacyData(size_t dataSize) override; + + void loadObject(std::string scope, std::string name, const JsonNode & data) override; + void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override; + + void loadSubObject(std::string name, JsonNode config, si32 ID, boost::optional subID = boost::optional()); + void removeSubObject(si32 ID, si32 subID); + + void beforeValidate(JsonNode & object) override; + void afterLoadFinalization() override; + + std::vector getDefaultAllowed() const override; + + /// Queries to detect loaded objects + std::set knownObjects() const; + std::set knownSubObjects(si32 primaryID) const; + + /// returns handler for specified object (ID-based). ObjectHandler keeps ownership + TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const; + + std::string getObjectName(si32 type) const; + + template void serialize(Handler &h, const int version) + { + h & objects; + } +}; diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp new file mode 100644 index 000000000..bdc06a286 --- /dev/null +++ b/lib/mapObjects/CObjectHandler.cpp @@ -0,0 +1,497 @@ +/* + * CObjectHandler.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CObjectHandler.h" + +#include "../NetPacks.h" +#include "../CGeneralTextHandler.h" +#include "../CHeroHandler.h" +#include "../CSoundBase.h" + +#include "CObjectClassesHandler.h" + +using namespace boost::assign; + +IGameCallback * IObjectInterface::cb = nullptr; + +///helpers +static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1) +{ + OpenWindow ow; + ow.window = type; + ow.id1 = id1; + ow.id2 = id2; + IObjectInterface::cb->sendAndApply(&ow); +} + +static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) +{ + InfoWindow iw; + iw.soundID = soundID; + iw.player = playerID; + iw.text.addTxt(MetaString::ADVOB_TXT,txtID); + IObjectInterface::cb->sendAndApply(&iw); +} + +/*static void showInfoDialog(const ObjectInstanceID heroID, const ui32 txtID, const ui16 soundID) +{ + const PlayerColor playerID = IObjectInterface::cb->getOwner(heroID); + showInfoDialog(playerID,txtID,soundID); +}*/ + +static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID) +{ + const PlayerColor playerID = h->getOwner(); + showInfoDialog(playerID,txtID,soundID); +} + +static std::string & visitedTxt(const bool visited) +{ + int id = visited ? 352 : 353; + return VLC->generaltexth->allTexts[id]; +} + +///IObjectInterface +void IObjectInterface::onHeroVisit(const CGHeroInstance * h) const +{} + +void IObjectInterface::onHeroLeave(const CGHeroInstance * h) const +{} + +void IObjectInterface::newTurn () const +{} + +IObjectInterface::~IObjectInterface() +{} + +IObjectInterface::IObjectInterface() +{} + +void IObjectInterface::initObj() +{} + +void IObjectInterface::setProperty( ui8 what, ui32 val ) +{} + +bool IObjectInterface::wasVisited (PlayerColor player) const +{ + return false; +} +bool IObjectInterface::wasVisited (const CGHeroInstance * h) const +{ + return false; +} + +void IObjectInterface::postInit() +{} + +void IObjectInterface::preInit() +{} + +void IObjectInterface::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{} + +void IObjectInterface::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{} + +void IObjectInterface::garrisonDialogClosed(const CGHeroInstance *hero) const +{} + +void IObjectInterface::heroLevelUpDone(const CGHeroInstance *hero) const +{} + +CObjectHandler::CObjectHandler() +{ + logGlobal->traceStream() << "\t\tReading resources prices "; + const JsonNode config2(ResourceID("config/resources.json")); + for(const JsonNode &price : config2["resources_prices"].Vector()) + { + resVals.push_back(price.Float()); + } + logGlobal->traceStream() << "\t\tDone loading resource prices!"; +} + +PlayerColor CGObjectInstance::getOwner() const +{ + //if (state) + // return state->owner; + //else + return tempOwner; //won't have owner +} + +CGObjectInstance::CGObjectInstance(): + pos(-1,-1,-1), + ID(Obj::NO_OBJ), + subID(-1), + tempOwner(PlayerColor::UNFLAGGABLE), + blockVisit(false) +{ +} +CGObjectInstance::~CGObjectInstance() +{ + //if (state) + // delete state; + //state=nullptr; +} + +const std::string & CGObjectInstance::getHoverText() const +{ + return hoverName; +} +void CGObjectInstance::setOwner(PlayerColor ow) +{ + //if (state) + // state->owner = ow; + //else + tempOwner = ow; +} +int CGObjectInstance::getWidth() const//returns width of object graphic in tiles +{ + return appearance.getWidth(); +} +int CGObjectInstance::getHeight() const //returns height of object graphic in tiles +{ + return appearance.getHeight(); +} +bool CGObjectInstance::visitableAt(int x, int y) const //returns true if object is visitable at location (x, y) form left top tile of image (x, y in tiles) +{ + return appearance.isVisitableAt(pos.x - x, pos.y - y); +} +bool CGObjectInstance::blockingAt(int x, int y) const +{ + return appearance.isBlockedAt(pos.x - x, pos.y - y); +} + +bool CGObjectInstance::coveringAt(int x, int y) const +{ + return appearance.isVisibleAt(pos.x - x, pos.y - y); +} + +std::set CGObjectInstance::getBlockedPos() const +{ + std::set ret; + for(int w=0; w CGObjectInstance::getBlockedOffsets() const +{ + std::set ret; + for(int w=0; w cmp.appearance.printPriority; + + if(pos.y != cmp.pos.y) + return pos.y < cmp.pos.y; + + if(cmp.ID==Obj::HERO && ID!=Obj::HERO) + return true; + if(cmp.ID!=Obj::HERO && ID==Obj::HERO) + return false; + + if(!isVisitable() && cmp.isVisitable()) + return true; + if(!cmp.isVisitable() && isVisitable()) + return false; + if(this->pos.xgameState()->map->getTile(visitablePos()); + + this->ID = Obj(ID); + this->subID = subID; + + //recalculate blockvis tiles - new appearance might have different blockmap than before + cb->gameState()->map->removeBlockVisTiles(this, true); + auto handler = VLC->objtypeh->getHandlerFor(ID, subID); + appearance = handler->getTemplates(tile.terType).at(0); + cb->gameState()->map->addBlockVisTiles(this); +} + +void CGObjectInstance::initObj() +{ + switch(ID) + { + case Obj::TAVERN: + blockVisit = true; + break; + } +} + +void CGObjectInstance::setProperty( ui8 what, ui32 val ) +{ + switch(what) + { + case ObjProperty::OWNER: + tempOwner = PlayerColor(val); + break; + case ObjProperty::BLOCKVIS: + blockVisit = val; + break; + case ObjProperty::ID: + ID = Obj(val); + break; + case ObjProperty::SUBID: + subID = val; + break; + } + setPropertyDer(what, val); +} + +void CGObjectInstance::setPropertyDer( ui8 what, ui32 val ) +{} + +int3 CGObjectInstance::getSightCenter() const +{ + //return vistiable tile if possible + for(int i=0; i < 8; i++) + for(int j=0; j < 6; j++) + if(visitableAt(i,j)) + return(pos + int3(i-7, j-5, 0)); + return pos; +} + +int CGObjectInstance::getSightRadious() const +{ + return 3; +} +void CGObjectInstance::getSightTiles(std::unordered_set &tiles) const //returns reference to the set +{ + cb->getTilesInRange(tiles, getSightCenter(), getSightRadious(), tempOwner, 1); +} +void CGObjectInstance::hideTiles(PlayerColor ourplayer, int radius) const +{ + for (auto i = cb->gameState()->teams.begin(); i != cb->gameState()->teams.end(); i++) + { + if ( !vstd::contains(i->second.players, ourplayer ))//another team + { + for (auto & elem : i->second.players) + if ( cb->getPlayer(elem)->status == EPlayerStatus::INGAME )//seek for living player (if any) + { + FoWChange fw; + fw.mode = 0; + fw.player = elem; + cb->getTilesInRange (fw.tiles, pos, radius, (elem), -1); + cb->sendAndApply (&fw); + break; + } + } + } +} +int3 CGObjectInstance::getVisitableOffset() const +{ + for(int y = 0; y < appearance.getHeight(); y++) + for (int x = 0; x < appearance.getWidth(); x++) + if (appearance.isVisitableAt(x, y)) + return int3(x,y,0); + + logGlobal->warnStream() << "Warning: getVisitableOffset called on non-visitable obj!"; + return int3(0,0,0); +} + +void CGObjectInstance::getNameVis( std::string &hname ) const +{ + const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); + hname = VLC->objtypeh->getObjectName(ID); + if(h) + { + const bool visited = h->hasBonusFrom(Bonus::OBJECT,ID); + hname + " " + visitedTxt(visited); + } +} + +void CGObjectInstance::giveDummyBonus(ObjectInstanceID heroID, ui8 duration) const +{ + GiveBonus gbonus; + gbonus.bonus.type = Bonus::NONE; + gbonus.id = heroID.getNum(); + gbonus.bonus.duration = duration; + gbonus.bonus.source = Bonus::OBJECT; + gbonus.bonus.sid = ID; + cb->giveHeroBonus(&gbonus); +} + +void CGObjectInstance::onHeroVisit( const CGHeroInstance * h ) const +{ + switch(ID) + { + case Obj::HILL_FORT: + { + openWindow(OpenWindow::HILL_FORT_WINDOW,id.getNum(),h->id.getNum()); + } + break; + case Obj::SANCTUARY: + { + //You enter the sanctuary and immediately feel as if a great weight has been lifted off your shoulders. You feel safe here. + showInfoDialog(h,114,soundBase::GETPROTECTION); + } + break; + case Obj::TAVERN: + { + openWindow(OpenWindow::TAVERN_WINDOW,h->id.getNum(),id.getNum()); + } + break; + } +} + +ui8 CGObjectInstance::getPassableness() const +{ + return 0; +} + +int3 CGObjectInstance::visitablePos() const +{ + return pos - getVisitableOffset(); +} + +bool CGObjectInstance::isVisitable() const +{ + return appearance.isVisitable(); +} + +bool CGObjectInstance::passableFor(PlayerColor color) const +{ + return getPassableness() & 1<obj->subID == obj->subID; +} + +int3 IBoatGenerator::bestLocation() const +{ + std::vector offsets; + getOutOffsets(offsets); + + for (auto & offset : offsets) + { + if (const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map + { + if (tile->terType == ETerrainType::WATER && (!tile->blocked || tile->blockingObjects.front()->ID == 8)) //and is water and is not blocked or is blocked by boat + return o->pos + offset; + } + } + return int3 (-1,-1,-1); +} + +IBoatGenerator::EGeneratorState IBoatGenerator::shipyardStatus() const +{ + int3 tile = bestLocation(); + const TerrainTile *t = IObjectInterface::cb->getTile(tile); + if(!t) + return TILE_BLOCKED; //no available water + else if(!t->blockingObjects.size()) + return GOOD; //OK + else if(t->blockingObjects.front()->ID == Obj::BOAT) + return BOAT_ALREADY_BUILT; //blocked with boat + else + return TILE_BLOCKED; //blocked +} + +int IBoatGenerator::getBoatType() const +{ + //We make good ships by default + return 1; +} + + +IBoatGenerator::IBoatGenerator(const CGObjectInstance *O) +: o(O) +{ +} + +void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visitor) const +{ + switch(shipyardStatus()) + { + case BOAT_ALREADY_BUILT: + out.addTxt(MetaString::GENERAL_TXT, 51); + break; + case TILE_BLOCKED: + if(visitor) + { + out.addTxt(MetaString::GENERAL_TXT, 134); + out.addReplacement(visitor->name); + } + else + out.addTxt(MetaString::ADVOB_TXT, 189); + break; + case NO_WATER: + logGlobal->errorStream() << "Shipyard without water!!! " << o->pos << "\t" << o->id; + return; + } +} + +void IShipyard::getBoatCost( std::vector &cost ) const +{ + cost.resize(GameConstants::RESOURCE_QUANTITY); + cost[Res::WOOD] = 10; + cost[Res::GOLD] = 1000; +} + +IShipyard::IShipyard(const CGObjectInstance *O) + : IBoatGenerator(O) +{ +} + +IShipyard * IShipyard::castFrom( CGObjectInstance *obj ) +{ + if(!obj) + return nullptr; + + if(obj->ID == Obj::TOWN) + { + return static_cast(obj); + } + else if(obj->ID == Obj::SHIPYARD) + { + return static_cast(obj); + } + else + { + return nullptr; + } +} + +const IShipyard * IShipyard::castFrom( const CGObjectInstance *obj ) +{ + return castFrom(const_cast(obj)); +} diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h new file mode 100644 index 000000000..e1031a123 --- /dev/null +++ b/lib/mapObjects/CObjectHandler.h @@ -0,0 +1,177 @@ +#pragma once + +#include "ObjectTemplate.h" + +#include "../IGameCallback.h" +#include "../int3.h" +#include "../HeroBonus.h" + +/* + * CObjectHandler.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 + * + */ + +class CGHeroInstance; +struct BattleResult; + +class DLL_LINKAGE IObjectInterface +{ +public: + static IGameCallback *cb; + + IObjectInterface(); + virtual ~IObjectInterface(); + + virtual void onHeroVisit(const CGHeroInstance * h) const; + virtual void onHeroLeave(const CGHeroInstance * h) const; + virtual void newTurn() const; + virtual void initObj(); //synchr + virtual void setProperty(ui8 what, ui32 val);//synchr + + //Called when queries created DURING HERO VISIT are resolved + //First parameter is always hero that visited object and triggered the query + virtual void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const; + virtual void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const; + virtual void garrisonDialogClosed(const CGHeroInstance *hero) const; + virtual void heroLevelUpDone(const CGHeroInstance *hero) const; + +//unified interface, AI helpers + virtual bool wasVisited (PlayerColor player) const; + virtual bool wasVisited (const CGHeroInstance * h) const; + + static void preInit(); //called before objs receive their initObj + static void postInit();//called after objs receive their initObj + + template void serialize(Handler &h, const int version) + { + logGlobal->errorStream() << "IObjectInterface serialized, unexpected, should not happen!"; + } +}; + +class DLL_LINKAGE IBoatGenerator +{ +public: + const CGObjectInstance *o; + + IBoatGenerator(const CGObjectInstance *O); + virtual ~IBoatGenerator() {} + + virtual int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral + virtual void getOutOffsets(std::vector &offsets) const =0; //offsets to obj pos when we boat can be placed + int3 bestLocation() const; //returns location when the boat should be placed + + enum EGeneratorState {GOOD, BOAT_ALREADY_BUILT, TILE_BLOCKED, NO_WATER}; + EGeneratorState shipyardStatus() const; //0 - can buid, 1 - there is already a boat at dest tile, 2 - dest tile is blocked, 3 - no water + void getProblemText(MetaString &out, const CGHeroInstance *visitor = nullptr) const; + + template void serialize(Handler &h, const int version) + { + h & o; + } +}; + +class DLL_LINKAGE IShipyard : public IBoatGenerator +{ +public: + IShipyard(const CGObjectInstance *O); + virtual ~IShipyard() {} + + virtual void getBoatCost(std::vector &cost) const; + + static const IShipyard *castFrom(const CGObjectInstance *obj); + static IShipyard *castFrom(CGObjectInstance *obj); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGObjectInstance : public IObjectInterface +{ +public: + mutable std::string hoverName; + int3 pos; //h3m pos + Obj ID; + si32 subID; //normal subID (this one from OH3 maps ;]) + ObjectInstanceID id;//number of object in map's vector + ObjectTemplate appearance; + + PlayerColor tempOwner; + bool blockVisit; //if non-zero then blocks the tile but is visitable from neighbouring tile + + virtual ui8 getPassableness() const; //bitmap - if the bit is set the corresponding player can pass through the visitable tiles of object, even if it's blockvis; if not set - default properties from definfo are used + virtual int3 getSightCenter() const; //"center" tile from which the sight distance is calculated + virtual int getSightRadious() const; //sight distance (should be used if player-owned structure) + bool passableFor(PlayerColor color) const; + void getSightTiles(std::unordered_set &tiles) const; //returns reference to the set + PlayerColor getOwner() const; + void setOwner(PlayerColor ow); + int getWidth() const; //returns width of object graphic in tiles + int getHeight() const; //returns height of object graphic in tiles + virtual bool visitableAt(int x, int y) const; //returns true if object is visitable at location (x, y) (h3m pos) + virtual int3 getVisitableOffset() const; //returns (x,y,0) offset to first visitable tile from bottom right obj tile (0,0,0) (h3m pos) + int3 visitablePos() const; + bool blockingAt(int x, int y) const; //returns true if object is blocking location (x, y) (h3m pos) + bool coveringAt(int x, int y) const; //returns true if object covers with picture location (x, y) (h3m pos) + std::set getBlockedPos() const; //returns set of positions blocked by this object + std::set getBlockedOffsets() const; //returns set of relative positions blocked by this object + bool isVisitable() const; //returns true if object is visitable + bool operator<(const CGObjectInstance & cmp) const; //screen printing priority comparing + void hideTiles(PlayerColor ourplayer, int radius) const; + CGObjectInstance(); + virtual ~CGObjectInstance(); + //CGObjectInstance(const CGObjectInstance & right); + //CGObjectInstance& operator=(const CGObjectInstance & right); + virtual const std::string & getHoverText() const; + + virtual void setType(si32 ID, si32 subID); + + ///IObjectInterface + void initObj() override; + void onHeroVisit(const CGHeroInstance * h) const override; + void setProperty(ui8 what, ui32 val) override;//synchr + + friend class CGameHandler; + + + template void serialize(Handler &h, const int version) + { + h & hoverName & pos & ID & subID & id & tempOwner & blockVisit & appearance; + //definfo is handled by map serializer + } +protected: + virtual void setPropertyDer(ui8 what, ui32 val);//synchr + + void getNameVis(std::string &hname) const; + void giveDummyBonus(ObjectInstanceID heroID, ui8 duration = Bonus::ONE_DAY) const; +}; + +/// function object which can be used to find an object with an specific sub ID +class CGObjectInstanceBySubIdFinder +{ +public: + CGObjectInstanceBySubIdFinder(CGObjectInstance * obj); + bool operator()(CGObjectInstance * obj) const; + +private: + CGObjectInstance * obj; +}; + +class DLL_LINKAGE CObjectHandler +{ +public: + std::vector resVals; //default values of resources in gold + + CObjectHandler(); + + template void serialize(Handler &h, const int version) + { + h & resVals; + } +}; diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp new file mode 100644 index 000000000..c899b4071 --- /dev/null +++ b/lib/mapObjects/CQuest.cpp @@ -0,0 +1,862 @@ +/* + * + * CQuest.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CQuest.h" + +#include "../NetPacks.h" +#include "../CSoundBase.h" +#include "../CGeneralTextHandler.h" +#include "../CHeroHandler.h" +#include "CObjectClassesHandler.h" + +using namespace boost::assign; + +std::map > CGKeys::playerKeyMap; + +///helpers +static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) +{ + InfoWindow iw; + iw.soundID = soundID; + iw.player = playerID; + iw.text.addTxt(MetaString::ADVOB_TXT,txtID); + IObjectInterface::cb->sendAndApply(&iw); +} + +static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID) +{ + const PlayerColor playerID = h->getOwner(); + showInfoDialog(playerID,txtID,soundID); +} + +static std::string & visitedTxt(const bool visited) +{ + int id = visited ? 352 : 353; + return VLC->generaltexth->allTexts[id]; +} + +bool CQuest::checkQuest (const CGHeroInstance * h) const +{ + switch (missionType) + { + case MISSION_NONE: + return true; + case MISSION_LEVEL: + if (m13489val <= h->level) + return true; + return false; + case MISSION_PRIMARY_STAT: + for (int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i) + { + if (h->getPrimSkillLevel(static_cast(i)) < m2stats[i]) + return false; + } + return true; + case MISSION_KILL_HERO: + case MISSION_KILL_CREATURE: + if (!h->cb->getObjByQuestIdentifier(m13489val)) + return true; + return false; + case MISSION_ART: + for (auto & elem : m5arts) + { + if (h->hasArt(elem)) + continue; + return false; //if the artifact was not found + } + return true; + case MISSION_ARMY: + { + std::vector::const_iterator cre; + TSlots::const_iterator it; + ui32 count; + for (cre = m6creatures.begin(); cre != m6creatures.end(); ++cre) + { + for (count = 0, it = h->Slots().begin(); it != h->Slots().end(); ++it) + { + if (it->second->type == cre->type) + count += it->second->count; + } + if (count < cre->count) //not enough creatures of this kind + return false; + } + } + return true; + case MISSION_RESOURCES: + for (Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, +1)) //including Mithril ? + { //Quest has no direct access to callback + if (h->cb->getResource (h->tempOwner, i) < m7resources[i]) + return false; + } + return true; + case MISSION_HERO: + if (m13489val == h->type->ID.getNum()) + return true; + return false; + case MISSION_PLAYER: + if (m13489val == h->getOwner().getNum()) + return true; + return false; + default: + return false; + } +} + +void CQuest::getVisitText (MetaString &iwText, std::vector &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const +{ + std::string text; + bool failRequirements = (h ? !checkQuest(h) : true); + + if (firstVisit) + { + isCustom = isCustomFirst; + iwText << (text = firstVisitText); + } + else if (failRequirements) + { + isCustom = isCustomNext; + iwText << (text = nextVisitText); + } + switch (missionType) + { + case MISSION_LEVEL: + components.push_back(Component (Component::EXPERIENCE, 0, m13489val, 0)); + if (!isCustom) + iwText.addReplacement(m13489val); + break; + case MISSION_PRIMARY_STAT: + { + MetaString loot; + for (int i = 0; i < 4; ++i) + { + if (m2stats[i]) + { + components.push_back(Component (Component::PRIM_SKILL, i, m2stats[i], 0)); + loot << "%d %s"; + loot.addReplacement(m2stats[i]); + loot.addReplacement(VLC->generaltexth->primarySkillNames[i]); + } + } + if (!isCustom) + iwText.addReplacement(loot.buildList()); + } + break; + case MISSION_KILL_HERO: + components.push_back(Component(Component::HERO_PORTRAIT, heroPortrait, 0, 0)); + if (!isCustom) + addReplacements(iwText, text); + break; + case MISSION_HERO: + //FIXME: portrait may not match hero, if custom portrait was set in map editor + components.push_back(Component (Component::HERO_PORTRAIT, VLC->heroh->heroes[m13489val]->imageIndex, 0, 0)); + if (!isCustom) + iwText.addReplacement(VLC->heroh->heroes[m13489val]->name); + break; + case MISSION_KILL_CREATURE: + { + components.push_back(Component(stackToKill)); + if (!isCustom) + { + addReplacements(iwText, text); + } + } + break; + case MISSION_ART: + { + MetaString loot; + for (auto & elem : m5arts) + { + components.push_back(Component (Component::ARTIFACT, elem, 0, 0)); + loot << "%s"; + loot.addReplacement(MetaString::ART_NAMES, elem); + } + if (!isCustom) + iwText.addReplacement(loot.buildList()); + } + break; + case MISSION_ARMY: + { + MetaString loot; + for (auto & elem : m6creatures) + { + components.push_back(Component(elem)); + loot << "%s"; + loot.addReplacement(elem); + } + if (!isCustom) + iwText.addReplacement(loot.buildList()); + } + break; + case MISSION_RESOURCES: + { + MetaString loot; + for (int i = 0; i < 7; ++i) + { + if (m7resources[i]) + { + components.push_back(Component (Component::RESOURCE, i, m7resources[i], 0)); + loot << "%d %s"; + loot.addReplacement(m7resources[i]); + loot.addReplacement(MetaString::RES_NAMES, i); + } + } + if (!isCustom) + iwText.addReplacement(loot.buildList()); + } + break; + case MISSION_PLAYER: + components.push_back(Component (Component::FLAG, m13489val, 0, 0)); + if (!isCustom) + iwText.addReplacement(VLC->generaltexth->colors[m13489val]); + break; + } +} + +void CQuest::getRolloverText (MetaString &ms, bool onHover) const +{ + if (onHover) + ms << "\n\n"; + + ms << VLC->generaltexth->quests[missionType-1][onHover ? 3 : 4][textOption]; + + switch (missionType) + { + case MISSION_LEVEL: + ms.addReplacement(m13489val); + break; + case MISSION_PRIMARY_STAT: + { + MetaString loot; + for (int i = 0; i < 4; ++i) + { + if (m2stats[i]) + { + loot << "%d %s"; + loot.addReplacement(m2stats[i]); + loot.addReplacement(VLC->generaltexth->primarySkillNames[i]); + } + } + ms.addReplacement(loot.buildList()); + } + break; + case MISSION_KILL_HERO: + ms.addReplacement(heroName); + break; + case MISSION_KILL_CREATURE: + ms.addReplacement(stackToKill); + break; + case MISSION_ART: + { + MetaString loot; + for (auto & elem : m5arts) + { + loot << "%s"; + loot.addReplacement(MetaString::ART_NAMES, elem); + } + ms.addReplacement(loot.buildList()); + } + break; + case MISSION_ARMY: + { + MetaString loot; + for (auto & elem : m6creatures) + { + loot << "%s"; + loot.addReplacement(elem); + } + ms.addReplacement(loot.buildList()); + } + break; + case MISSION_RESOURCES: + { + MetaString loot; + for (int i = 0; i < 7; ++i) + { + if (m7resources[i]) + { + loot << "%d %s"; + loot.addReplacement(m7resources[i]); + loot.addReplacement(MetaString::RES_NAMES, i); + } + } + ms.addReplacement(loot.buildList()); + } + break; + case MISSION_HERO: + ms.addReplacement(VLC->heroh->heroes[m13489val]->name); + break; + case MISSION_PLAYER: + ms.addReplacement(VLC->generaltexth->colors[m13489val]); + break; + default: + break; + } +} + +void CQuest::getCompletionText (MetaString &iwText, std::vector &components, bool isCustom, const CGHeroInstance * h) const +{ + iwText << completedText; + switch (missionType) + { + case CQuest::MISSION_LEVEL: + if (!isCustomComplete) + iwText.addReplacement(m13489val); + break; + case CQuest::MISSION_PRIMARY_STAT: + if (vstd::contains (completedText,'%')) //there's one case when there's nothing to replace + { + MetaString loot; + for (int i = 0; i < 4; ++i) + { + if (m2stats[i]) + { + loot << "%d %s"; + loot.addReplacement(m2stats[i]); + loot.addReplacement(VLC->generaltexth->primarySkillNames[i]); + } + } + if (!isCustomComplete) + iwText.addReplacement(loot.buildList()); + } + break; + case CQuest::MISSION_ART: + { + MetaString loot; + for (auto & elem : m5arts) + { + loot << "%s"; + loot.addReplacement(MetaString::ART_NAMES, elem); + } + if (!isCustomComplete) + iwText.addReplacement(loot.buildList()); + } + break; + case CQuest::MISSION_ARMY: + { + MetaString loot; + for (auto & elem : m6creatures) + { + loot << "%s"; + loot.addReplacement(elem); + } + if (!isCustomComplete) + iwText.addReplacement(loot.buildList()); + } + break; + case CQuest::MISSION_RESOURCES: + { + MetaString loot; + for (int i = 0; i < 7; ++i) + { + if (m7resources[i]) + { + loot << "%d %s"; + loot.addReplacement(m7resources[i]); + loot.addReplacement(MetaString::RES_NAMES, i); + } + } + if (!isCustomComplete) + iwText.addReplacement(loot.buildList()); + } + break; + case MISSION_KILL_HERO: + case MISSION_KILL_CREATURE: + if (!isCustomComplete) + addReplacements(iwText, completedText); + break; + case MISSION_HERO: + if (!isCustomComplete) + iwText.addReplacement(VLC->heroh->heroes[m13489val]->name); + break; + case MISSION_PLAYER: + if (!isCustomComplete) + iwText.addReplacement(VLC->generaltexth->colors[m13489val]); + break; + } +} + +void CGSeerHut::setObjToKill() +{ + if (quest->missionType == CQuest::MISSION_KILL_CREATURE) + { + quest->stackToKill = getCreatureToKill(false)->getStack(SlotID(0)); //FIXME: stacks tend to disappear (desync?) on server :? + assert(quest->stackToKill.type); + quest->stackToKill.count = 0; //no count in info window + quest->stackDirection = checkDirection(); + } + else if (quest->missionType == CQuest::MISSION_KILL_HERO) + { + quest->heroName = getHeroToKill(false)->name; + quest->heroPortrait = getHeroToKill(false)->portrait; + } +} + +void CGSeerHut::init() +{ + seerName = *RandomGeneratorUtil::nextItem(VLC->generaltexth->seerNames, cb->gameState()->getRandomGenerator()); + quest->textOption = cb->gameState()->getRandomGenerator().nextInt(2); +} + +void CGSeerHut::initObj() +{ + init(); + + quest->progress = CQuest::NOT_ACTIVE; + if (quest->missionType) + { + if (!quest->isCustomFirst) + quest->firstVisitText = VLC->generaltexth->quests[quest->missionType-1][0][quest->textOption]; + if (!quest->isCustomNext) + quest->nextVisitText = VLC->generaltexth->quests[quest->missionType-1][1][quest->textOption]; + if (!quest->isCustomComplete) + quest->completedText = VLC->generaltexth->quests[quest->missionType-1][2][quest->textOption]; + } + else + { + quest->progress = CQuest::COMPLETE; + quest->firstVisitText = VLC->generaltexth->seerEmpty[quest->textOption]; + } +} + +void CGSeerHut::getRolloverText (MetaString &text, bool onHover) const +{ + quest->getRolloverText (text, onHover);//TODO: simplify? + if (!onHover) + text.addReplacement(seerName); +} + +const std::string & CGSeerHut::getHoverText() const +{ + switch (ID) + { + case Obj::SEER_HUT: + if (quest->progress != CQuest::NOT_ACTIVE) + { + hoverName = VLC->generaltexth->allTexts[347]; + boost::algorithm::replace_first(hoverName,"%s", seerName); + } + else //just seer hut + hoverName = VLC->objtypeh->getObjectName(ID); + break; + case Obj::QUEST_GUARD: + hoverName = VLC->objtypeh->getObjectName(ID); + break; + default: + logGlobal->debugStream() << "unrecognized quest object"; + } + if (quest->progress & quest->missionType) //rollover when the quest is active + { + MetaString ms; + getRolloverText (ms, true); + hoverName += ms.toString(); + } + return hoverName; +} + +void CQuest::addReplacements(MetaString &out, const std::string &base) const +{ + switch(missionType) + { + case MISSION_KILL_CREATURE: + out.addReplacement(stackToKill); + if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster + { + out.addReplacement(VLC->generaltexth->arraytxt[147+stackDirection]); + } + break; + case MISSION_KILL_HERO: + out.addReplacement(heroName); + break; + } +} + +bool IQuestObject::checkQuest(const CGHeroInstance* h) const +{ + return quest->checkQuest(h); +} + +void IQuestObject::getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const +{ + quest->getVisitText (text,components, isCustom, FirstVisit, h); +} + +void CGSeerHut::getCompletionText(MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h) const +{ + quest->getCompletionText (text, components, isCustom, h); + switch (rewardType) + { + case EXPERIENCE: components.push_back(Component (Component::EXPERIENCE, 0, h->calculateXp(rVal), 0)); + break; + case MANA_POINTS: components.push_back(Component (Component::PRIM_SKILL, 5, rVal, 0)); + break; + case MORALE_BONUS: components.push_back(Component (Component::MORALE, 0, rVal, 0)); + break; + case LUCK_BONUS: components.push_back(Component (Component::LUCK, 0, rVal, 0)); + break; + case RESOURCES: components.push_back(Component (Component::RESOURCE, rID, rVal, 0)); + break; + case PRIMARY_SKILL: components.push_back(Component (Component::PRIM_SKILL, rID, rVal, 0)); + break; + case SECONDARY_SKILL: components.push_back(Component (Component::SEC_SKILL, rID, rVal, 0)); + break; + case ARTIFACT: components.push_back(Component (Component::ARTIFACT, rID, 0, 0)); + break; + case SPELL: components.push_back(Component (Component::SPELL, rID, 0, 0)); + break; + case CREATURE: components.push_back(Component (Component::CREATURE, rID, rVal, 0)); + break; + } +} + +void CGSeerHut::setPropertyDer (ui8 what, ui32 val) +{ + switch (what) + { + case 10: + quest->progress = static_cast(val); + break; + } +} + +void CGSeerHut::newTurn() const +{ + if (quest->lastDay >= 0 && quest->lastDay < cb->getDate()-1) //time is up + { + cb->setObjProperty (id, 10, CQuest::COMPLETE); + } +} + +void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const +{ + InfoWindow iw; + iw.player = h->getOwner(); + if (quest->progress < CQuest::COMPLETE) + { + bool firstVisit = !quest->progress; + bool failRequirements = !checkQuest(h); + bool isCustom=false; + + if (firstVisit) + { + isCustom = quest->isCustomFirst; + cb->setObjProperty (id, 10, CQuest::IN_PROGRESS); + + AddQuest aq; + aq.quest = QuestInfo (quest, this, visitablePos()); + aq.player = h->tempOwner; + cb->sendAndApply (&aq); //TODO: merge with setObjProperty? + } + else if (failRequirements) + { + isCustom = quest->isCustomNext; + } + + if (firstVisit || failRequirements) + { + getVisitText (iw.text, iw.components, isCustom, firstVisit, h); + + cb->showInfoDialog(&iw); + } + if (!failRequirements) // propose completion, also on first visit + { + BlockingDialog bd (true, false); + bd.player = h->getOwner(); + bd.soundID = soundBase::QUEST; + + getCompletionText (bd.text, bd.components, isCustom, h); + + cb->showBlockingDialog (&bd); + return; + } + } + else + { + iw.text << VLC->generaltexth->seerEmpty[quest->textOption]; + if (ID == Obj::SEER_HUT) + iw.text.addReplacement(seerName); + cb->showInfoDialog(&iw); + } +} + +int CGSeerHut::checkDirection() const +{ + int3 cord = getCreatureToKill()->pos; + if ((double)cord.x/(double)cb->getMapSize().x < 0.34) //north + { + if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //northwest + return 8; + else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //north + return 1; + else //northeast + return 2; + } + else if ((double)cord.x/(double)cb->getMapSize().x < 0.67) //horizontal + { + if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //west + return 7; + else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //central + return 9; + else //east + return 3; + } + else //south + { + if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //southwest + return 6; + else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //south + return 5; + else //southeast + return 4; + } +} + +void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const +{ + if (accept) + { + switch (quest->missionType) + { + case CQuest::MISSION_ART: + for (auto & elem : quest->m5arts) + { + cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false))); + } + break; + case CQuest::MISSION_ARMY: + cb->takeCreatures(h->id, quest->m6creatures); + break; + case CQuest::MISSION_RESOURCES: + for (int i = 0; i < 7; ++i) + { + cb->giveResource(h->getOwner(), static_cast(i), -quest->m7resources[i]); + } + break; + default: + break; + } + cb->setObjProperty (id, 10, CQuest::COMPLETE); //mission complete + completeQuest(h); //make sure to remove QuestGuard at the very end + } +} + +void CGSeerHut::completeQuest (const CGHeroInstance * h) const //reward +{ + switch (rewardType) + { + case EXPERIENCE: + { + TExpType expVal = h->calculateXp(rVal); + cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false); + break; + } + case MANA_POINTS: + { + cb->setManaPoints(h->id, h->mana+rVal); + break; + } + case MORALE_BONUS: case LUCK_BONUS: + { + Bonus hb(Bonus::ONE_WEEK, (rewardType == 3 ? Bonus::MORALE : Bonus::LUCK), + Bonus::OBJECT, rVal, h->id.getNum(), "", -1); + GiveBonus gb; + gb.id = h->id.getNum(); + gb.bonus = hb; + cb->giveHeroBonus(&gb); + } + break; + case RESOURCES: + cb->giveResource(h->getOwner(), static_cast(rID), rVal); + break; + case PRIMARY_SKILL: + cb->changePrimSkill(h, static_cast(rID), rVal, false); + break; + case SECONDARY_SKILL: + cb->changeSecSkill(h, SecondarySkill(rID), rVal, false); + break; + case ARTIFACT: + cb->giveHeroNewArtifact(h, VLC->arth->artifacts[rID],ArtifactPosition::FIRST_AVAILABLE); + break; + case SPELL: + { + std::set spell; + spell.insert (SpellID(rID)); + cb->changeSpells(h, true, spell); + } + break; + case CREATURE: + { + CCreatureSet creatures; + creatures.setCreature(SlotID(0), CreatureID(rID), rVal); + cb->giveCreatures(this, h, creatures, false); + } + break; + default: + break; + } +} + +const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const +{ + const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val); + if(allowNull && !o) + return nullptr; + assert(o && (o->ID == Obj::HERO || o->ID == Obj::PRISON)); + return static_cast(o); +} + +const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const +{ + const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val); + if(allowNull && !o) + return nullptr; + assert(o && o->ID == Obj::MONSTER); + return static_cast(o); +} + +void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + finishQuest(hero, answer); +} + +void CGQuestGuard::init() +{ + blockVisit = true; + quest->textOption = cb->gameState()->getRandomGenerator().nextInt(3, 5); +} + +void CGQuestGuard::completeQuest(const CGHeroInstance *h) const +{ + cb->removeObject(this); +} + +void CGKeys::setPropertyDer (ui8 what, ui32 val) //101-108 - enable key for player 1-8 +{ + if (what >= 101 && what <= (100 + PlayerColor::PLAYER_LIMIT_I)) + { + PlayerColor player(what-101); + playerKeyMap[player].insert((ui8)val); + } + else + logGlobal->errorStream() << boost::format("Unexpected properties requested to set: what=%d, val=%d") % (int)what % val; +} + +bool CGKeys::wasMyColorVisited (PlayerColor player) const +{ + if (vstd::contains(playerKeyMap[player], subID)) //creates set if it's not there + return true; + else + return false; +} + +const std::string& CGKeys::getHoverText() const +{ + bool visited = wasMyColorVisited (cb->getLocalPlayer()); + hoverName = getName() + "\n" + visitedTxt(visited); + return hoverName; +} + + +const std::string CGKeys::getName() const +{ + std::string name; + name = VLC->generaltexth->tentColors[subID] + " " + VLC->objtypeh->getObjectName(ID); + return name; +} + +bool CGKeymasterTent::wasVisited (PlayerColor player) const +{ + return wasMyColorVisited (player); +} + +void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const +{ + int txt_id; + if (!wasMyColorVisited (h->getOwner()) ) + { + cb->setObjProperty(id, h->tempOwner.getNum()+101, subID); + txt_id=19; + } + else + txt_id=20; + showInfoDialog(h,txt_id,soundBase::CAVEHEAD); +} + +void CGBorderGuard::initObj() +{ + //ui32 m13489val = subID; //store color as quest info + blockVisit = true; +} + +void CGBorderGuard::getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const +{ + text << std::pair(11,18); +} + +void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const +{ + if (!onHover) + text << VLC->generaltexth->tentColors[subID] << " " << VLC->objtypeh->getObjectName(Obj::KEYMASTER); +} + +bool CGBorderGuard::checkQuest (const CGHeroInstance * h) const +{ + return wasMyColorVisited (h->tempOwner); +} + +void CGBorderGuard::onHeroVisit( const CGHeroInstance * h ) const +{ + if (wasMyColorVisited (h->getOwner()) ) + { + BlockingDialog bd (true, false); + bd.player = h->getOwner(); + bd.soundID = soundBase::QUEST; + bd.text.addTxt (MetaString::ADVOB_TXT, 17); + cb->showBlockingDialog (&bd); + } + else + { + showInfoDialog(h,18,soundBase::CAVEHEAD); + + AddQuest aq; + aq.quest = QuestInfo (quest, this, visitablePos()); + aq.player = h->tempOwner; + cb->sendAndApply (&aq); + //TODO: add this quest only once OR check for multiple instances later + } +} + +void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if (answer) + cb->removeObject(this); +} + +void CGBorderGate::onHeroVisit( const CGHeroInstance * h ) const //TODO: passability +{ + if (!wasMyColorVisited (h->getOwner()) ) + { + showInfoDialog(h,18,0); + + AddQuest aq; + aq.quest = QuestInfo (quest, this, visitablePos()); + aq.player = h->tempOwner; + cb->sendAndApply (&aq); + } +} + +ui8 CGBorderGate::getPassableness() const +{ + ui8 ret = 0; + for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) + ret |= wasMyColorVisited(PlayerColor(i))< m2stats; + std::vector m5arts; //artifacts id + std::vector m6creatures; //pair[cre id, cre count], CreatureSet info irrelevant + std::vector m7resources; //TODO: use resourceset? + + //following field are used only for kill creature/hero missions, the original objects became inaccessible after their removal, so we need to store info needed for messages / hover text + ui8 textOption; + CStackBasicDescriptor stackToKill; + ui8 stackDirection; + std::string heroName; //backup of hero name + si32 heroPortrait; + + std::string firstVisitText, nextVisitText, completedText; + bool isCustomFirst, isCustomNext, isCustomComplete; + + CQuest(){missionType = MISSION_NONE;}; //default constructor + virtual ~CQuest(){}; + + virtual bool checkQuest (const CGHeroInstance * h) const; //determines whether the quest is complete or not + virtual void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; + virtual void getCompletionText (MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h = nullptr) const; + virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry + virtual void completeQuest (const CGHeroInstance * h) const {}; + virtual void addReplacements(MetaString &out, const std::string &base) const; + + bool operator== (const CQuest & quest) const + { + return (quest.qid == qid); + } + + template void serialize(Handler &h, const int version) + { + h & qid & missionType & progress & lastDay & m13489val & m2stats & m5arts & m6creatures & m7resources + & textOption & stackToKill & stackDirection & heroName & heroPortrait + & firstVisitText & nextVisitText & completedText & isCustomFirst & isCustomNext & isCustomComplete; + } +}; + +class DLL_LINKAGE IQuestObject +{ +public: + CQuest * quest; + + IQuestObject(): quest(new CQuest()){}; + virtual ~IQuestObject() {}; + virtual void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; + virtual bool checkQuest (const CGHeroInstance * h) const; + + template void serialize(Handler &h, const int version) + { + h & quest; + } +}; + +class DLL_LINKAGE CGSeerHut : public CArmedInstance, public IQuestObject //army is used when giving reward +{ +public: + enum ERewardType {NOTHING, EXPERIENCE, MANA_POINTS, MORALE_BONUS, LUCK_BONUS, RESOURCES, PRIMARY_SKILL, SECONDARY_SKILL, ARTIFACT, SPELL, CREATURE}; + ERewardType rewardType; + si32 rID; //reward ID + si32 rVal; //reward value + std::string seerName; + + CGSeerHut() : IQuestObject(){}; + void initObj() override; + const std::string & getHoverText() const override; + void newTurn() const override; + void onHeroVisit(const CGHeroInstance * h) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + virtual void init(); + int checkDirection() const; //calculates the region of map where monster is placed + void setObjToKill(); //remember creatures / heroes to kill after they are initialized + const CGHeroInstance *getHeroToKill(bool allowNull = false) const; + const CGCreature *getCreatureToKill(bool allowNull = false) const; + void getRolloverText (MetaString &text, bool onHover) const; + void getCompletionText(MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h = nullptr) const; + void finishQuest (const CGHeroInstance * h, ui32 accept) const; //common for both objects + virtual void completeQuest (const CGHeroInstance * h) const; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this) & static_cast(*this); + h & rewardType & rID & rVal & seerName; + } +protected: + void setPropertyDer(ui8 what, ui32 val) override; +}; + +class DLL_LINKAGE CGQuestGuard : public CGSeerHut +{ +public: + CGQuestGuard() : CGSeerHut(){}; + void init() override; + void completeQuest (const CGHeroInstance * h) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGKeys : public CGObjectInstance //Base class for Keymaster and guards +{ +public: + static std::map > playerKeyMap; //[players][keysowned] + //SubID 0 - lightblue, 1 - green, 2 - red, 3 - darkblue, 4 - brown, 5 - purple, 6 - white, 7 - black + + const std::string getName() const; //depending on color + bool wasMyColorVisited (PlayerColor player) const; + + const std::string & getHoverText() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +protected: + void setPropertyDer(ui8 what, ui32 val) override; +}; + +class DLL_LINKAGE CGKeymasterTent : public CGKeys +{ +public: + bool wasVisited (PlayerColor player) const; + void onHeroVisit(const CGHeroInstance * h) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject +{ +public: + CGBorderGuard() : IQuestObject(){}; + void initObj() override; + void onHeroVisit(const CGHeroInstance * h) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; + void getRolloverText (MetaString &text, bool onHover) const; + bool checkQuest (const CGHeroInstance * h) const; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + h & blockVisit; + } +}; + +class DLL_LINKAGE CGBorderGate : public CGBorderGuard +{ +public: + CGBorderGate() : CGBorderGuard(){}; + void onHeroVisit(const CGHeroInstance * h) const override; + + ui8 getPassableness() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); //need to serialize or object will be empty + } +}; diff --git a/lib/mapObjects/CRewardableConstructor.cpp b/lib/mapObjects/CRewardableConstructor.cpp new file mode 100644 index 000000000..e5b02e0c9 --- /dev/null +++ b/lib/mapObjects/CRewardableConstructor.cpp @@ -0,0 +1,201 @@ +#include "StdInc.h" +#include "CRewardableConstructor.h" + +#include "../CRandomGenerator.h" +#include "../StringConstants.h" +#include "../CCreatureHandler.h" +#include "JsonRandom.h" + +/* + * CRewardableConstructor.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +namespace { + MetaString loadMessage(const JsonNode & value) + { + MetaString ret; + if (value.getType() == JsonNode::DATA_FLOAT) + ret.addTxt(MetaString::ADVOB_TXT, value.Float()); + else + ret << value.String(); + return ret; + } + + bool testForKey(const JsonNode & value, const std::string & key) + { + for( auto & reward : value["rewards"].Vector() ) + { + if (!reward[key].isNull()) + return true; + } + return false; + } +} + +void CRandomRewardObjectInfo::init(const JsonNode & objectConfig) +{ + parameters = objectConfig; +} + +void CRandomRewardObjectInfo::configureObject(CRewardableObject * object, CRandomGenerator & rng) const +{ + std::map thrownDice; + + for (const JsonNode & reward : parameters["rewards"].Vector()) + { + if (!reward["appearChance"].isNull()) + { + JsonNode chance = reward["appearChance"]; + si32 diceID = chance["dice"].Float(); + + if (thrownDice.count(diceID) == 0) + thrownDice[diceID] = rng.getIntRange(1, 100)(); + + if (!chance["min"].isNull()) + { + int min = chance["min"].Float(); + if (min > thrownDice[diceID]) + continue; + } + if (!chance["max"].isNull()) + { + int max = chance["max"].Float(); + if (max < thrownDice[diceID]) + continue; + } + } + + const JsonNode & limiter = reward["limiter"]; + CVisitInfo info; + // load limiter + info.limiter.numOfGrants = JsonRandom::loadValue(limiter["numOfGrants"], rng); + info.limiter.dayOfWeek = JsonRandom::loadValue(limiter["dayOfWeek"], rng); + info.limiter.minLevel = JsonRandom::loadValue(limiter["minLevel"], rng); + info.limiter.resources = JsonRandom::loadResources(limiter["resources"], rng); + + info.limiter.primary = JsonRandom::loadPrimary(limiter["primary"], rng); + info.limiter.secondary = JsonRandom::loadSecondary(limiter["secondary"], rng); + info.limiter.artifacts = JsonRandom::loadArtifacts(limiter["artifacts"], rng); + info.limiter.creatures = JsonRandom::loadCreatures(limiter["creatures"], rng); + + info.reward.resources = JsonRandom::loadResources(reward["resources"], rng); + + info.reward.gainedExp = JsonRandom::loadValue(reward["gainedExp"], rng); + info.reward.gainedLevels = JsonRandom::loadValue(reward["gainedLevels"], rng); + + info.reward.manaDiff = JsonRandom::loadValue(reward["manaPoints"], rng); + info.reward.manaPercentage = JsonRandom::loadValue(reward["manaPercentage"], rng, -1); + + info.reward.movePoints = JsonRandom::loadValue(reward["movePoints"], rng); + info.reward.movePercentage = JsonRandom::loadValue(reward["movePercentage"], rng, -1); + + info.reward.bonuses = JsonRandom::loadBonuses(reward["bonuses"]); + + info.reward.primary = JsonRandom::loadPrimary(reward["primary"], rng); + info.reward.secondary = JsonRandom::loadSecondary(reward["secondary"], rng); + + std::vector spells; + for (size_t i=0; i<6; i++) + IObjectInterface::cb->getAllowedSpells(spells, i); + + info.reward.artifacts = JsonRandom::loadArtifacts(reward["artifacts"], rng); + info.reward.spells = JsonRandom::loadSpells(reward["spells"], rng, spells); + info.reward.creatures = JsonRandom::loadCreatures(reward["creatures"], rng); + + info.message = loadMessage(reward["message"]); + info.selectChance = JsonRandom::loadValue(reward["selectChance"], rng); + } + + object->onSelect = loadMessage(parameters["onSelectMessage"]); + object->onVisited = loadMessage(parameters["onVisitedMessage"]); + object->onEmpty = loadMessage(parameters["onEmptyMessage"]); + + //TODO: visitMode and selectMode + + object->soundID = parameters["soundID"].Float(); + object->resetDuration = parameters["resetDuration"].Float(); + object->canRefuse =parameters["canRefuse"].Bool(); +} + +bool CRandomRewardObjectInfo::givesResources() const +{ + return testForKey(parameters, "resources"); +} + +bool CRandomRewardObjectInfo::givesExperience() const +{ + return testForKey(parameters, "gainedExp") || testForKey(parameters, "gainedLevels"); +} + +bool CRandomRewardObjectInfo::givesMana() const +{ + return testForKey(parameters, "manaPoints") || testForKey(parameters, "manaPercentage"); +} + +bool CRandomRewardObjectInfo::givesMovement() const +{ + return testForKey(parameters, "movePoints") || testForKey(parameters, "movePercentage"); +} + +bool CRandomRewardObjectInfo::givesPrimarySkills() const +{ + return testForKey(parameters, "primary"); +} + +bool CRandomRewardObjectInfo::givesSecondarySkills() const +{ + return testForKey(parameters, "secondary"); +} + +bool CRandomRewardObjectInfo::givesArtifacts() const +{ + return testForKey(parameters, "artifacts"); +} + +bool CRandomRewardObjectInfo::givesCreatures() const +{ + return testForKey(parameters, "spells"); +} + +bool CRandomRewardObjectInfo::givesSpells() const +{ + return testForKey(parameters, "creatures"); +} + +bool CRandomRewardObjectInfo::givesBonuses() const +{ + return testForKey(parameters, "bonuses"); +} + +CRewardableConstructor::CRewardableConstructor() +{ +} + +void CRewardableConstructor::initTypeData(const JsonNode & config) +{ + AObjectTypeHandler::init(config); + objectInfo.init(config); +} + +CGObjectInstance * CRewardableConstructor::create(ObjectTemplate tmpl) const +{ + auto ret = new CRewardableObject(); + ret->appearance = tmpl; + return ret; +} + +void CRewardableConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const +{ + objectInfo.configureObject(dynamic_cast(object), rng); +} + +std::unique_ptr CRewardableConstructor::getObjectInfo(ObjectTemplate tmpl) const +{ + return std::unique_ptr(new CRandomRewardObjectInfo(objectInfo)); +} diff --git a/lib/mapObjects/CRewardableConstructor.h b/lib/mapObjects/CRewardableConstructor.h new file mode 100644 index 000000000..171b5a6ca --- /dev/null +++ b/lib/mapObjects/CRewardableConstructor.h @@ -0,0 +1,58 @@ +#pragma once + +#include "CRewardableObject.h" +#include "CObjectClassesHandler.h" + +#include "../JsonNode.h" + +/* + * CRewardableConstructor.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 + * + */ + +class CRandomRewardObjectInfo : public IObjectInfo +{ + JsonNode parameters; +public: + bool givesResources() const override; + + bool givesExperience() const override; + bool givesMana() const override; + bool givesMovement() const override; + + bool givesPrimarySkills() const override; + bool givesSecondarySkills() const override; + + bool givesArtifacts() const override; + bool givesCreatures() const override; + bool givesSpells() const override; + + bool givesBonuses() const override; + + void configureObject(CRewardableObject * object, CRandomGenerator & rng) const; + + CRandomRewardObjectInfo() + {} + + void init(const JsonNode & objectConfig); +}; + +class CRewardableConstructor : public AObjectTypeHandler +{ + CRandomRewardObjectInfo objectInfo; + + void initTypeData(const JsonNode & config) override; +public: + CRewardableConstructor(); + + CGObjectInstance * create(ObjectTemplate tmpl) const override; + + void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override; + + std::unique_ptr getObjectInfo(ObjectTemplate tmpl) const override; +}; diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp new file mode 100644 index 000000000..5fc3731ff --- /dev/null +++ b/lib/mapObjects/CRewardableObject.cpp @@ -0,0 +1,1099 @@ +/* + * + * CRewardableObject.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CRewardableObject.h" +#include "../CHeroHandler.h" +#include "../CGeneralTextHandler.h" +#include "../CSoundBase.h" +#include "../NetPacks.h" + +#include "CObjectClassesHandler.h" + +bool CRewardLimiter::heroAllowed(const CGHeroInstance * hero) const +{ + if (dayOfWeek != 0) + { + if (IObjectInterface::cb->getDate(Date::DAY_OF_WEEK) != dayOfWeek) + return false; + } + + for (auto & reqStack : creatures) + { + size_t count = 0; + for (auto slot : hero->Slots()) + { + const CStackInstance * heroStack = slot.second; + if (heroStack->type == reqStack.type) + count += heroStack->count; + } + if (count < reqStack.count) //not enough creatures of this kind + return false; + } + + if (!IObjectInterface::cb->getPlayer(hero->tempOwner)->resources.canAfford(resources)) + return false; + + if (minLevel > hero->level) + return false; + + for (size_t i=0; i hero->getPrimSkillLevel(PrimarySkill::PrimarySkill(i))) + return false; + } + + for (auto & skill : secondary) + { + if (skill.second > hero->getSecSkillLevel(skill.first)) + return false; + } + + for (auto & art : artifacts) + { + if (!hero->hasArt(art)) + return false; + } + + return true; +} + +std::vector CRewardableObject::getAvailableRewards(const CGHeroInstance * hero) const +{ + std::vector ret; + + for (size_t i=0; i void + { + grantReward(index, h); + // show message only if it is not empty + if (!info[index].message.toString().empty()) + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.soundID = soundID; + iw.text = info[index].message; + info[index].reward.loadComponents(iw.components); + cb->showInfoDialog(&iw); + } + }; + auto selectRewardsMessage = [&](std::vector rewards) -> void + { + BlockingDialog sd(canRefuse, rewards.size() > 1); + sd.player = h->tempOwner; + sd.soundID = soundID; + sd.text = onSelect; + for (auto index : rewards) + sd.components.push_back(info[index].reward.getDisplayedComponent()); + cb->showBlockingDialog(&sd); + }; + + if (!wasVisited(h)) + { + auto rewards = getAvailableRewards(h); + switch (rewards.size()) + { + case 0: // no available rewards, e.g. empty flotsam + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.soundID = soundID; + if (!onEmpty.toString().empty()) + iw.text = onEmpty; + else + iw.text = onVisited; + cb->showInfoDialog(&iw); + break; + } + case 1: // one reward. Just give it with message + { + if (canRefuse) + selectRewardsMessage(rewards); + else + grantRewardWithMessage(rewards[0]); + break; + } + default: // multiple rewards. Act according to select mode + { + switch (selectMode) { + case SELECT_PLAYER: // player must select + selectRewardsMessage(rewards); + break; + case SELECT_FIRST: // give first available + grantRewardWithMessage(rewards[0]); + break; + case SELECT_RANDOM: // select one randomly //TODO: use weights + grantRewardWithMessage(rewards[cb->gameState()->getRandomGenerator().nextInt(rewards.size()-1)]); + break; + } + return; + } + } + } + else + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.soundID = soundID; + if (!onVisited.toString().empty()) + iw.text = onVisited; + else + iw.text = onEmpty; + cb->showInfoDialog(&iw); + } +} + +void CRewardableObject::heroLevelUpDone(const CGHeroInstance *hero) const +{ + grantRewardAfterLevelup(info[selectedReward], hero); +} + +void CRewardableObject::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if (answer == 0) + return; // player refused + + if (answer > 0 && answer-1 < info.size()) + { + //NOTE: this relies on assumption that there won't be any changes in player/hero during blocking dialog + auto list = getAvailableRewards(hero); + grantReward(list[answer - 1], hero); + } + else + { + throw std::runtime_error("Unhandled choice"); + } +} + +void CRewardableObject::onRewardGiven(const CGHeroInstance * hero) const +{ + // no implementation, virtual function for overrides +} + +void CRewardableObject::grantReward(ui32 rewardID, const CGHeroInstance * hero) const +{ + ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD, id, hero->id); + cb->sendAndApply(&cov); + cb->setObjProperty(id, ObjProperty::REWARD_SELECT, rewardID); + + grantRewardBeforeLevelup(info[rewardID], hero); +} + +void CRewardableObject::grantRewardBeforeLevelup(const CVisitInfo & info, const CGHeroInstance * hero) const +{ + assert(hero); + assert(hero->tempOwner.isValidPlayer()); + assert(stacks.empty()); + assert(info.reward.creatures.size() <= GameConstants::ARMY_SIZE); + assert(!cb->isVisitCoveredByAnotherQuery(this, hero)); + + cb->giveResources(hero->tempOwner, info.reward.resources); + + for (auto & entry : info.reward.secondary) + { + int current = hero->getSecSkillLevel(entry.first); + if( (current != 0 && current < entry.second) || + (hero->canLearnSkill() )) + { + cb->changeSecSkill(hero, entry.first, entry.second); + } + } + + for(int i=0; i< info.reward.primary.size(); i++) + if(info.reward.primary[i] > 0) + cb->changePrimSkill(hero, static_cast(i), info.reward.primary[i], false); + + si64 expToGive = 0; + expToGive += VLC->heroh->reqExp(hero->level+info.reward.gainedLevels) - VLC->heroh->reqExp(hero->level); + expToGive += hero->calculateXp(info.reward.gainedExp); + if (expToGive) + cb->changePrimSkill(hero, PrimarySkill::EXPERIENCE, expToGive); + + // hero is not blocked by levelup dialog - grant remainer immediately + if (!cb->isVisitCoveredByAnotherQuery(this, hero)) + { + grantRewardAfterLevelup(info, hero); + } +} + +void CRewardableObject::grantRewardAfterLevelup(const CVisitInfo & info, const CGHeroInstance * hero) const +{ + if (info.reward.manaDiff || info.reward.manaPercentage >= 0) + { + si32 mana = hero->mana; + if (info.reward.manaPercentage >= 0) + mana = hero->manaLimit() * info.reward.manaPercentage / 100; + + cb->setManaPoints(hero->id, mana + info.reward.manaDiff); + } + + if(info.reward.movePoints || info.reward.movePercentage >= 0) + { + SetMovePoints smp; + smp.hid = hero->id; + smp.val = hero->movement; + + if (info.reward.movePercentage >= 0) // percent from max + smp.val = hero->maxMovePoints(hero->boat != nullptr) * info.reward.movePercentage / 100; + smp.val = std::max(0, smp.val + info.reward.movePoints); + + cb->setMovePoints(&smp); + } + + for (const Bonus & bonus : info.reward.bonuses) + { + GiveBonus gb; + gb.bonus = bonus; + gb.id = hero->id.getNum(); + cb->giveHeroBonus(&gb); + } + + for (ArtifactID art : info.reward.artifacts) + cb->giveHeroNewArtifact(hero, VLC->arth->artifacts[art],ArtifactPosition::FIRST_AVAILABLE); + + if (!info.reward.spells.empty()) + { + std::set spellsToGive(info.reward.spells.begin(), info.reward.spells.end()); + cb->changeSpells(hero, true, spellsToGive); + } + + if (!info.reward.creatures.empty()) + { + CCreatureSet creatures; + for (auto & crea : info.reward.creatures) + creatures.addToSlot(creatures.getFreeSlot(), new CStackInstance(crea.type, crea.count)); + + cb->giveCreatures(this, hero, creatures, false); + } + + onRewardGiven(hero); + + if (info.reward.removeObject) + cb->removeObject(this); +} + +bool CRewardableObject::wasVisited (PlayerColor player) const +{ + switch (visitMode) + { + case VISIT_UNLIMITED: + return false; + case VISIT_ONCE: + for (auto & visit : info) + { + if (visit.numOfGrants != 0) + return true; + } + case VISIT_HERO: + return false; + case VISIT_PLAYER: + return vstd::contains(cb->getPlayer(player)->visitedObjects, ObjectInstanceID(ID)); + default: + return false; + } +} + +bool CRewardableObject::wasVisited (const CGHeroInstance * h) const +{ + switch (visitMode) + { + case VISIT_HERO: + return vstd::contains(h->visitedObjects, ObjectInstanceID(ID)) || h->hasBonusFrom(Bonus::OBJECT, ID); + default: + return wasVisited(h->tempOwner); + } +} + +void CRewardInfo::loadComponents(std::vector & comps) const +{ + for (auto comp : extraComponents) + comps.push_back(comp); + + for (size_t i=0; iidNumber, entry.count, 0)); +} + +Component CRewardInfo::getDisplayedComponent() const +{ + std::vector comps; + loadComponents(comps); + assert(!comps.empty()); + return comps.front(); +} + +// FIXME: copy-pasted from CObjectHandler +static std::string & visitedTxt(const bool visited) +{ + int id = visited ? 352 : 353; + return VLC->generaltexth->allTexts[id]; +} + +const std::string & CRewardableObject::getHoverText() const +{ + const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); + hoverName = VLC->objtypeh->getObjectName(ID); + if(visitMode != VISIT_UNLIMITED) + { + bool visited = wasVisited(cb->getCurrentPlayer()); + if (h) + visited |= wasVisited(h); + + hoverName += " " + visitedTxt(visited); + } + return hoverName; +} + +void CRewardableObject::setPropertyDer(ui8 what, ui32 val) +{ + switch (what) + { + case ObjProperty::REWARD_RESET: + for (auto & visit : info) + visit.numOfGrants = 0; + break; + case ObjProperty::REWARD_SELECT: + selectedReward = val; + info[val].numOfGrants++; + break; + } +} + +void CRewardableObject::newTurn() const +{ + if (resetDuration != 0 && cb->getDate(Date::DAY) % resetDuration == 0) + cb->setObjProperty(id, ObjProperty::REWARD_RESET, 0); +} + +CRewardableObject::CRewardableObject(): + soundID(soundBase::invalid), + selectMode(0), + selectedReward(0), + resetDuration(0), + canRefuse(false) +{} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// END OF CODE FOR CREWARDABLEOBJECT AND RELATED CLASSES /// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Helper, selects random art class based on weights +static int selectRandomArtClass(int treasure, int minor, int major, int relic) +{ + int total = treasure + minor + major + relic; + assert(total != 0); + int hlp = IObjectInterface::cb->gameState()->getRandomGenerator().nextInt(total - 1); + + if(hlp < treasure) + return CArtifact::ART_TREASURE; + if(hlp < treasure + minor) + return CArtifact::ART_MINOR; + if(hlp < treasure + minor + major) + return CArtifact::ART_MAJOR; + return CArtifact::ART_RELIC; +} + +/// Helper, adds random artifact to reward selecting class based on weights +static void loadRandomArtifact(CVisitInfo & info, int treasure, int minor, int major, int relic) +{ + int artClass = selectRandomArtClass(treasure, minor, major, relic); + ArtifactID artID = VLC->arth->pickRandomArtifact(IObjectInterface::cb->gameState()->getRandomGenerator(), artClass); + info.reward.artifacts.push_back(artID); +} + +CGPickable::CGPickable() +{ + visitMode = VISIT_UNLIMITED; + selectMode = SELECT_PLAYER; +} + +void CGPickable::initObj() +{ + blockVisit = true; + switch(ID) + { + case Obj::CAMPFIRE: //FIXME: campfire is not functioning correctly in game (no visible message) + { + soundID = soundBase::experience; + int givenRes = cb->gameState()->getRandomGenerator().nextInt(5); + int givenAmm = cb->gameState()->getRandomGenerator().nextInt(4, 6); + + info.resize(1); + info[0].reward.resources[givenRes] = givenAmm; + info[0].reward.resources[Res::GOLD]= givenAmm * 100; + info[0].message.addTxt(MetaString::ADVOB_TXT,23); + info[0].reward.removeObject = true; + break; + } + case Obj::FLOTSAM: + { + int type = cb->gameState()->getRandomGenerator().nextInt(3); + soundID = soundBase::GENIE; + switch(type) + { + case 0: + info.resize(1); + info[0].message.addTxt(MetaString::ADVOB_TXT, 51); + info[0].reward.removeObject = true; + case 1: + { + info.resize(1); + info[0].reward.resources[Res::WOOD] = 5; + info[0].message.addTxt(MetaString::ADVOB_TXT, 52); + info[0].reward.removeObject = true; + break; + } + case 2: + { + info.resize(1); + info[0].reward.resources[Res::WOOD] = 5; + info[0].reward.resources[Res::GOLD] = 200; + info[0].message.addTxt(MetaString::ADVOB_TXT, 53); + info[0].reward.removeObject = true; + break; + } + case 3: + { + info.resize(1); + info[0].reward.resources[Res::WOOD] = 10; + info[0].reward.resources[Res::GOLD] = 500; + info[0].message.addTxt(MetaString::ADVOB_TXT, 54); + info[0].reward.removeObject = true; + break; + } + } + break; + } + case Obj::SEA_CHEST: + { + soundID = soundBase::chest; + int hlp = cb->gameState()->getRandomGenerator().nextInt(99); + if(hlp < 20) + { + info.resize(1); + info[0].message.addTxt(MetaString::ADVOB_TXT, 116); + info[0].reward.removeObject = true; + } + else if(hlp < 90) + { + info.resize(1); + info[0].reward.resources[Res::GOLD] = 1500; + info[0].message.addTxt(MetaString::ADVOB_TXT, 118); + info[0].reward.removeObject = true; + } + else + { + info.resize(1); + loadRandomArtifact(info[0], 100, 0, 0, 0); + info[0].reward.resources[Res::GOLD] = 1000; + info[0].message.addTxt(MetaString::ADVOB_TXT, 117); + info[0].message.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); + info[0].reward.removeObject = true; + } + } + break; + case Obj::SHIPWRECK_SURVIVOR: + { + soundID = soundBase::experience; + info.resize(1); + loadRandomArtifact(info[0], 55, 20, 20, 5); + info[0].message.addTxt(MetaString::ADVOB_TXT, 125); + info[0].message.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); + info[0].reward.removeObject = true; + } + break; + case Obj::TREASURE_CHEST: + { + int hlp = cb->gameState()->getRandomGenerator().nextInt(99); + if(hlp >= 95) + { + soundID = soundBase::treasure; + info.resize(1); + loadRandomArtifact(info[0], 100, 0, 0, 0); + info[0].message.addTxt(MetaString::ADVOB_TXT,145); + info[0].message.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); + info[0].reward.removeObject = true; + return; + } + else if (hlp >= 65) + { + soundID = soundBase::chest; + onSelect.addTxt(MetaString::ADVOB_TXT,146); + info.resize(2); + info[0].reward.resources[Res::GOLD] = 2000; + info[1].reward.gainedExp = 1500; + info[0].reward.removeObject = true; + info[1].reward.removeObject = true; + } + else if(hlp >= 33) + { + soundID = soundBase::chest; + onSelect.addTxt(MetaString::ADVOB_TXT,146); + info.resize(2); + info[0].reward.resources[Res::GOLD] = 1500; + info[1].reward.gainedExp = 1000; + info[0].reward.removeObject = true; + info[1].reward.removeObject = true; + } + else + { + soundID = soundBase::chest; + onSelect.addTxt(MetaString::ADVOB_TXT,146); + info.resize(2); + info[0].reward.resources[Res::GOLD] = 1000; + info[1].reward.gainedExp = 500; + info[0].reward.removeObject = true; + info[1].reward.removeObject = true; + } + } + break; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +CGBonusingObject::CGBonusingObject() +{ + visitMode = VISIT_UNLIMITED; + selectMode = SELECT_FIRST; +} + +void CGBonusingObject::initObj() +{ + auto configureBonusDuration = [&](CVisitInfo & visit, Bonus::BonusDuration duration, Bonus::BonusType type, si32 value, si32 descrID) + { + Bonus b(duration, type, Bonus::OBJECT, value, ID, descrID != 0 ? VLC->generaltexth->advobtxt[descrID] : ""); + visit.reward.bonuses.push_back(b); + if (type == Bonus::MORALE) + visit.reward.extraComponents.push_back(Component(Component::MORALE, 0, value, 0)); + if (type == Bonus::LUCK) + visit.reward.extraComponents.push_back(Component(Component::LUCK, 0, value, 0)); + }; + + auto configureBonus = [&](CVisitInfo & visit, Bonus::BonusType type, si32 value, si32 descrID) + { + configureBonusDuration(visit, Bonus::ONE_BATTLE, type, value, descrID); + }; + + auto configureMessage = [&](CVisitInfo & visit, int onGrantID, int onVisitedID, soundBase::soundID sound) + { + visit.message.addTxt(MetaString::ADVOB_TXT, onGrantID); + onVisited.addTxt(MetaString::ADVOB_TXT, onVisitedID); + soundID = sound; + }; + + info.resize(1); + + switch(ID) + { + case Obj::BUOY: + blockVisit = true; + configureMessage(info[0], 21, 22, soundBase::MORALE); + configureBonus(info[0], Bonus::MORALE, +1, 94); + break; + case Obj::SWAN_POND: + configureMessage(info[0], 29, 30, soundBase::LUCK); + configureBonus(info[0], Bonus::LUCK, 2, 67); + info[0].reward.movePercentage = 0; + break; + case Obj::FAERIE_RING: + configureMessage(info[0], 49, 50, soundBase::LUCK); + configureBonus(info[0], Bonus::LUCK, 2, 71); + break; + case Obj::FOUNTAIN_OF_FORTUNE: + selectMode = SELECT_RANDOM; + info.resize(5); + for (int i=0; i<5; i++) + { + configureBonus(info[i], Bonus::LUCK, i-1, 69); //NOTE: description have %d that should be replaced with value + configureMessage(info[i], 55, 56, soundBase::LUCK); + } + break; + case Obj::IDOL_OF_FORTUNE: + + info.resize(7); + for (int i=0; i<6; i++) + { + info[i].limiter.dayOfWeek = i+1; + configureBonus(info[i], i%2 ? Bonus::MORALE : Bonus::LUCK, 1, 68); + configureMessage(info[i], 62, 63, soundBase::experience); + } + info.back().limiter.dayOfWeek = 7; + configureBonus(info.back(), Bonus::MORALE, 1, 68); // on last day of week + configureBonus(info.back(), Bonus::LUCK, 1, 68); + configureMessage(info.back(), 62, 63, soundBase::experience); + + break; + case Obj::MERMAID: + blockVisit = true; + configureMessage(info[0], 83, 82, soundBase::LUCK); + configureBonus(info[0], Bonus::LUCK, 1, 72); + break; + case Obj::RALLY_FLAG: + configureMessage(info[0], 111, 110, soundBase::MORALE); + configureBonus(info[0], Bonus::MORALE, 1, 102); + configureBonus(info[0], Bonus::LUCK, 1, 102); + info[0].reward.movePoints = 400; + break; + case Obj::OASIS: + configureMessage(info[0], 95, 94, soundBase::MORALE); + configureBonus(info[0], Bonus::MORALE, 1, 95); + info[0].reward.movePoints = 800; + break; + case Obj::TEMPLE: + info[0].limiter.dayOfWeek = 7; + info.resize(2); + configureBonus(info[0], Bonus::MORALE, 2, 96); + configureBonus(info[1], Bonus::MORALE, 1, 97); + + configureMessage(info[0], 140, 141, soundBase::temple); + configureMessage(info[1], 140, 141, soundBase::temple); + break; + case Obj::WATERING_HOLE: + configureMessage(info[0], 166, 167, soundBase::MORALE); + configureBonus(info[0], Bonus::MORALE, 1, 100); + info[0].reward.movePoints = 400; + break; + case Obj::FOUNTAIN_OF_YOUTH: + configureMessage(info[0], 57, 58, soundBase::MORALE); + configureBonus(info[0], Bonus::MORALE, 1, 103); + info[0].reward.movePoints = 400; + break; + case Obj::STABLES: + configureMessage(info[0], 137, 136, soundBase::STORE); + + configureBonusDuration(info[0], Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, 600, 0); + info[0].reward.movePoints = 600; + //TODO: upgrade champions to cavaliers +/* + bool someUpgradeDone = false; + + for (auto i = h->Slots().begin(); i != h->Slots().end(); ++i) + { + if(i->second->type->idNumber == CreatureID::CAVALIER) + { + cb->changeStackType(StackLocation(h, i->first), VLC->creh->creatures[CreatureID::CHAMPION]); + someUpgradeDone = true; + } + } + if (someUpgradeDone) + { + grantMessage.addTxt(MetaString::ADVOB_TXT, 138); + iw.components.push_back(Component(Component::CREATURE,11,0,1)); + }*/ + break; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +CGOnceVisitable::CGOnceVisitable() +{ + visitMode = VISIT_ONCE; + selectMode = SELECT_FIRST; +} + +void CGOnceVisitable::initObj() +{ + switch(ID) + { + case Obj::CORPSE: + { + onEmpty.addTxt(MetaString::ADVOB_TXT, 38); + soundID = soundBase::MYSTERY; + blockVisit = true; + if(cb->gameState()->getRandomGenerator().nextInt(99) < 20) + { + info.resize(1); + loadRandomArtifact(info[0], 10, 10, 10, 0); + info[0].message.addTxt(MetaString::ADVOB_TXT, 37); + } + } + break; + case Obj::LEAN_TO: + { + soundID = soundBase::GENIE; + onEmpty.addTxt(MetaString::ADVOB_TXT, 65); + info.resize(1); + int type = cb->gameState()->getRandomGenerator().nextInt(5); //any basic resource without gold + int value = cb->gameState()->getRandomGenerator().nextInt(1, 4); + info[0].reward.resources[type] = value; + info[0].message.addTxt(MetaString::ADVOB_TXT, 64); + } + break; + case Obj::WARRIORS_TOMB: + { + soundID = soundBase::GRAVEYARD; + onSelect.addTxt(MetaString::ADVOB_TXT, 161); + + info.resize(2); + loadRandomArtifact(info[0], 30, 50, 25, 5); + + Bonus bonus(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::OBJECT, -3, ID); + info[0].reward.bonuses.push_back(bonus); + info[1].reward.bonuses.push_back(bonus); + info[0].message.addTxt(MetaString::ADVOB_TXT, 162); + info[1].message.addTxt(MetaString::ADVOB_TXT, 163); + } + break; + case Obj::WAGON: + { + soundID = soundBase::GENIE; + onVisited.addTxt(MetaString::ADVOB_TXT, 156); + + int hlp = cb->gameState()->getRandomGenerator().nextInt(99); + + if(hlp < 40) //minor or treasure art + { + info.resize(1); + loadRandomArtifact(info[0], 10, 10, 0, 0); + info[0].message.addTxt(MetaString::ADVOB_TXT, 155); + } + else if(hlp < 90) //2 - 5 of non-gold resource + { + info.resize(1); + int type = cb->gameState()->getRandomGenerator().nextInt(5); + int value = cb->gameState()->getRandomGenerator().nextInt(2, 5); + info[0].reward.resources[type] = value; + info[0].message.addTxt(MetaString::ADVOB_TXT, 154); + } + // or nothing + } + break; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +CGVisitableOPH::CGVisitableOPH() +{ + visitMode = VISIT_HERO; + selectMode = SELECT_PLAYER; +} + +void CGVisitableOPH::initObj() +{ + switch(ID) + { + case Obj::ARENA: + soundID = soundBase::NOMAD; + info.resize(2); + info[0].reward.primary[PrimarySkill::ATTACK] = 2; + info[1].reward.primary[PrimarySkill::DEFENSE] = 2; + onSelect.addTxt(MetaString::ADVOB_TXT, 0); + onVisited.addTxt(MetaString::ADVOB_TXT, 1); + canRefuse = true; + break; + case Obj::MERCENARY_CAMP: + info.resize(1); + info[0].reward.primary[PrimarySkill::ATTACK] = 1; + soundID = soundBase::NOMAD; + info[0].message.addTxt(MetaString::ADVOB_TXT, 80); + onVisited.addTxt(MetaString::ADVOB_TXT, 81); + break; + case Obj::MARLETTO_TOWER: + info.resize(1); + info[0].reward.primary[PrimarySkill::DEFENSE] = 1; + soundID = soundBase::NOMAD; + info[0].message.addTxt(MetaString::ADVOB_TXT, 39); + onVisited.addTxt(MetaString::ADVOB_TXT, 40); + break; + case Obj::STAR_AXIS: + info.resize(1); + info[0].reward.primary[PrimarySkill::SPELL_POWER] = 1; + soundID = soundBase::gazebo; + info[0].message.addTxt(MetaString::ADVOB_TXT, 100); + onVisited.addTxt(MetaString::ADVOB_TXT, 101); + break; + case Obj::GARDEN_OF_REVELATION: + info.resize(1); + info[0].reward.primary[PrimarySkill::KNOWLEDGE] = 1; + soundID = soundBase::GETPROTECTION; + info[0].message.addTxt(MetaString::ADVOB_TXT, 59); + onVisited.addTxt(MetaString::ADVOB_TXT, 60); + break; + case Obj::LEARNING_STONE: + info.resize(1); + info[0].reward.gainedExp = 1000; + soundID = soundBase::gazebo; + info[0].message.addTxt(MetaString::ADVOB_TXT, 143); + onVisited.addTxt(MetaString::ADVOB_TXT, 144); + break; + case Obj::TREE_OF_KNOWLEDGE: + soundID = soundBase::gazebo; + info.resize(1); + canRefuse = true; + info[0].reward.gainedLevels = 1; + onVisited.addTxt(MetaString::ADVOB_TXT, 147); + info.resize(1); + switch (cb->gameState()->getRandomGenerator().nextInt(2)) + { + case 0: // free + onSelect.addTxt(MetaString::ADVOB_TXT, 148); + break; + case 1: + info[0].limiter.resources[Res::GOLD] = 2000; + info[0].reward.resources[Res::GOLD] = -2000; + onSelect.addTxt(MetaString::ADVOB_TXT, 149); + onEmpty.addTxt(MetaString::ADVOB_TXT, 150); + break; + case 2: + info[0].limiter.resources[Res::GEMS] = 10; + info[0].reward.resources[Res::GEMS] = -10; + onSelect.addTxt(MetaString::ADVOB_TXT, 151); + onEmpty.addTxt(MetaString::ADVOB_TXT, 152); + break; + } + break; + case Obj::LIBRARY_OF_ENLIGHTENMENT: + { + onVisited.addTxt(MetaString::ADVOB_TXT, 67); + onEmpty.addTxt(MetaString::ADVOB_TXT, 68); + + // Don't like this one but don't see any easier approach + CVisitInfo visit; + visit.reward.primary[PrimarySkill::ATTACK] = 2; + visit.reward.primary[PrimarySkill::DEFENSE] = 2; + visit.reward.primary[PrimarySkill::KNOWLEDGE] = 2; + visit.reward.primary[PrimarySkill::SPELL_POWER] = 2; + + static_assert(SecSkillLevel::LEVELS_SIZE == 4, "Behavior of Library of Enlignment may not be correct"); + for (int i=0; iobjtypeh->getObjectName(ID); + if(pom >= 0) + hoverName += ("\n" + VLC->generaltexth->xtrainfo[pom]); + const CGHeroInstance *h = cb->getSelectedHero (cb->getCurrentPlayer()); + if(h) + { + hoverName += "\n\n"; + bool visited = vstd::contains (visitors, h->id); + hoverName += visitedTxt (visited); + } + return hoverName; +} +*/ + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +CGVisitableOPW::CGVisitableOPW() +{ + visitMode = VISIT_ONCE; + selectMode = SELECT_RANDOM; + resetDuration = 7; +} + +void CGVisitableOPW::initObj() +{ + switch (ID) + { + case Obj::MYSTICAL_GARDEN: + soundID = soundBase::experience; + onEmpty.addTxt(MetaString::ADVOB_TXT, 93); + info.resize(2); + info[0].reward.resources[Res::GEMS] = 5; + info[1].reward.resources[Res::GOLD] = 500; + info[0].message.addTxt(MetaString::ADVOB_TXT, 92); + info[1].message.addTxt(MetaString::ADVOB_TXT, 92); + break; + case Obj::WINDMILL: + soundID = soundBase::GENIE; + onEmpty.addTxt(MetaString::ADVOB_TXT, 169); + // 3-6 of any resource but wood and gold + for (int resID = Res::MERCURY; resID < Res::GOLD; resID++) + { + for (int val = 3; val <=6; val++) + { + CVisitInfo visit; + visit.reward.resources[resID] = val; + visit.message.addTxt(MetaString::ADVOB_TXT, 92); + info.push_back(visit); + } + } + break; + case Obj::WATER_WHEEL: + soundID = soundBase::GENIE; + onEmpty.addTxt(MetaString::ADVOB_TXT, 165); + + info.resize(2); + info[0].limiter.dayOfWeek = 7; // double amount on sunday + info[0].reward.resources[Res::GOLD] = 1000; + info[1].reward.resources[Res::GOLD] = 500; + info[0].message.addTxt(MetaString::ADVOB_TXT, 164); + info[1].message.addTxt(MetaString::ADVOB_TXT, 164); + break; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void CGMagicSpring::initObj() +{ + CVisitInfo visit; // TODO: "player above max mana" limiter. Use logical expressions for limiters? + visit.reward.manaPercentage = 200; + visit.message.addTxt(MetaString::ADVOB_TXT, 74); + info.push_back(visit); // two rewards, one for each entrance + info.push_back(visit); + onEmpty.addTxt(MetaString::ADVOB_TXT, 75); + soundID = soundBase::GENIE; +} + +std::vector CGMagicSpring::getVisitableOffsets() const +{ + std::vector visitableTiles; + + for(int y = 0; y < 6; y++) + for (int x = 0; x < 8; x++) //starting from left + if (appearance.isVisitableAt(x, y)) + visitableTiles.push_back (int3(x, y , 0)); + + return visitableTiles; +} + +int3 CGMagicSpring::getVisitableOffset() const +{ + auto visitableTiles = getVisitableOffsets(); + + if (visitableTiles.size() != info.size()) + { + logGlobal->warnStream() << "Unexpected number of visitable tiles of Magic Spring at " << pos << "!"; + return int3(-1,-1,-1); + } + + for (size_t i=0; i CGMagicSpring::getAvailableRewards(const CGHeroInstance * hero) const +{ + auto tiles = getVisitableOffsets(); + for (size_t i=0; igetPosition() && info[i].numOfGrants == 0) + { + return std::vector(1, i); + } + } + // hero is either not on visitable tile (should not happen) or tile is already used + return std::vector(); +} diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h new file mode 100644 index 000000000..58d83c2c0 --- /dev/null +++ b/lib/mapObjects/CRewardableObject.h @@ -0,0 +1,349 @@ +#pragma once + +#include "CObjectHandler.h" +#include "CArmedInstance.h" + +#include "../NetPacksBase.h" + +/* + * CRewardableObject.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 + * + */ + +class CRandomRewardObjectInfo; + +/// Limiters of rewards. Rewards will be granted to hero only if he satisfies requirements +/// Note: for this is only a test - it won't remove anything from hero (e.g. artifacts or creatures) +/// NOTE: in future should (partially) replace seer hut/quest guard quests checks +class DLL_LINKAGE CRewardLimiter +{ +public: + /// how many times this reward can be granted, 0 for unlimited + si32 numOfGrants; + + /// day of week, unused if 0, 1-7 will test for current day of week + si32 dayOfWeek; + + /// level that hero needs to have + si32 minLevel; + + /// resources player needs to have in order to trigger reward + TResources resources; + + /// skills hero needs to have + std::vector primary; + std::map secondary; + + /// artifacts that hero needs to have (equipped or in backpack) to trigger this + /// Note: does not checks for multiple copies of the same arts + std::vector artifacts; + + /// creatures that hero needs to have + std::vector creatures; + + CRewardLimiter(): + numOfGrants(1), + dayOfWeek(0), + minLevel(0), + primary(4, 0) + {} + + bool heroAllowed(const CGHeroInstance * hero) const; + + template void serialize(Handler &h, const int version) + { + h & numOfGrants & dayOfWeek & minLevel & resources; + h & primary & secondary & artifacts & creatures; + } +}; + +/// Reward that can be granted to a hero +/// NOTE: eventually should replace seer hut rewards and events/pandoras +class DLL_LINKAGE CRewardInfo +{ +public: + /// resources that will be given to player + TResources resources; + + /// received experience + ui32 gainedExp; + /// received levels (converted into XP during grant) + ui32 gainedLevels; + + /// mana given to/taken from hero, fixed value + si32 manaDiff; + /// fixed value, in form of percentage from max + si32 manaPercentage; + + /// movement points, only for current day. Bonuses should be used to grant MP on any other day + si32 movePoints; + /// fixed value, in form of percentage from max + si32 movePercentage; + + /// list of bonuses, e.g. morale/luck + std::vector bonuses; + + /// skills that hero may receive or lose + std::vector primary; + std::map secondary; + + /// objects that hero may receive + std::vector artifacts; + std::vector spells; + std::vector creatures; + + /// list of components that will be added to reward description. First entry in list will override displayed component + std::vector extraComponents; + + /// if set to true, object will be removed after granting reward + bool removeObject; + + /// Generates list of components that describes reward + virtual void loadComponents(std::vector & comps) const; + Component getDisplayedComponent() const; + + CRewardInfo() : + gainedExp(0), + gainedLevels(0), + manaDiff(0), + manaPercentage(-1), + movePoints(0), + movePercentage(-1), + primary(4, 0), + removeObject(false) + {} + + template void serialize(Handler &h, const int version) + { + h & resources & extraComponents & removeObject; + h & manaPercentage & movePercentage; + h & gainedExp & gainedLevels & manaDiff & movePoints; + h & primary & secondary & bonuses; + h & artifacts & spells & creatures; + } +}; + +class CVisitInfo +{ +public: + CRewardLimiter limiter; + CRewardInfo reward; + + /// Message that will be displayed on granting of this reward, if not empty + MetaString message; + + /// Chance for this reward to be selected in case of random choice + si32 selectChance; + + /// How many times this reward has been granted since last reset + si32 numOfGrants; + + CVisitInfo(): + numOfGrants(0) + {} + + template void serialize(Handler &h, const int version) + { + h & limiter & reward & message & selectChance & numOfGrants; + } +}; + +/// Base class that can handle granting rewards to visiting heroes. +/// Inherits from CArmedInstance for proper trasfer of armies +class DLL_LINKAGE CRewardableObject : public CArmedInstance +{ + /// function that must be called if hero got level-up during grantReward call + void grantRewardAfterLevelup(const CVisitInfo & reward, const CGHeroInstance * hero) const; + + /// grants reward to hero + void grantRewardBeforeLevelup(const CVisitInfo & reward, const CGHeroInstance * hero) const; +protected: + /// controls selection of reward granted to player + enum ESelectMode + { + SELECT_FIRST, // first reward that matches limiters + SELECT_PLAYER, // player can select from all allowed rewards + SELECT_RANDOM // reward will be selected from allowed randomly + }; + + enum EVisitMode + { + VISIT_UNLIMITED, // any number of times. Side effect - object hover text won't contain visited/not visited text + VISIT_ONCE, // only once, first to visit get all the rewards + VISIT_HERO, // every hero can visit object once + VISIT_PLAYER // every player can visit object once + }; + + /// filters list of visit info and returns rewards that can be granted to current hero + virtual std::vector getAvailableRewards(const CGHeroInstance * hero) const; + + void grantReward(ui32 rewardID, const CGHeroInstance * hero) const; + + /// Rewars that can be granted by an object + std::vector info; + + /// MetaString's that contain text for messages for specific situations + MetaString onSelect; + MetaString onVisited; + MetaString onEmpty; + + /// sound that will be played alongside with *any* message + ui16 soundID; + /// how reward will be selected, uses ESelectMode enum + ui8 selectMode; + /// contols who can visit an object, uses EVisitMode enum + ui8 visitMode; + /// reward selected by player + ui16 selectedReward; + + /// object visitability info will be reset each resetDuration days + ui16 resetDuration; + + /// if true - player can refuse visiting an object (e.g. Tomb) + bool canRefuse; + +public: + void setPropertyDer(ui8 what, ui32 val) override; + const std::string & getHoverText() const override; + + /// Visitability checks. Note that hero check includes check for hero owner (returns true if object was visited by player) + bool wasVisited (PlayerColor player) const override; + bool wasVisited (const CGHeroInstance * h) const override; + + /// gives reward to player or ask for choice in case of multiple rewards + void onHeroVisit(const CGHeroInstance *h) const override; + + ///possibly resets object state + void newTurn() const override; + + /// gives second part of reward after hero level-ups for proper granting of spells/mana + void heroLevelUpDone(const CGHeroInstance *hero) const override; + + /// applies player selection of reward + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + /// function that will be called once reward is fully granted to hero + virtual void onRewardGiven(const CGHeroInstance * hero) const; + + CRewardableObject(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & info & canRefuse & resetDuration; + h & onSelect & onVisited & onEmpty & visitMode; + h & soundID & selectMode & selectedReward; + } + + // for configuration/object setup + friend class CRandomRewardObjectInfo; +}; + +class DLL_LINKAGE CGPickable : public CRewardableObject //campfire, treasure chest, Flotsam, Shipwreck Survivor, Sea Chest +{ +public: + void initObj() override; + + CGPickable(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGBonusingObject : public CRewardableObject //objects giving bonuses to luck/morale/movement +{ +public: + void initObj() override; + + CGBonusingObject(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGOnceVisitable : public CRewardableObject // wagon, corpse, lean to, warriors tomb +{ +public: + void initObj() override; + + CGOnceVisitable(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGVisitableOPH : public CRewardableObject //objects visitable only once per hero +{ +public: + void initObj() override; + + CGVisitableOPH(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGVisitableOPW : public CRewardableObject //objects visitable once per week +{ +public: + void initObj() override; + + CGVisitableOPW(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +///Special case - magic spring that has two separate visitable entrances +class DLL_LINKAGE CGMagicSpring : public CGVisitableOPW +{ +protected: + std::vector getAvailableRewards(const CGHeroInstance * hero) const override; + +public: + void initObj() override; + std::vector getVisitableOffsets() const; + int3 getVisitableOffset() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +//TODO: + +// MAX +// class DLL_LINKAGE CGPandoraBox : public CArmedInstance +// class DLL_LINKAGE CGEvent : public CGPandoraBox //event objects +// class DLL_LINKAGE CGSeerHut : public CArmedInstance, public IQuestObject //army is used when giving reward +// class DLL_LINKAGE CGQuestGuard : public CGSeerHut +// class DLL_LINKAGE CBank : public CArmedInstance +// class DLL_LINKAGE CGPyramid : public CBank + +// EXTRA +// class DLL_LINKAGE COPWBonus : public CGTownBuilding +// class DLL_LINKAGE CTownBonus : public CGTownBuilding +// class DLL_LINKAGE CGKeys : public CGObjectInstance //Base class for Keymaster and guards +// class DLL_LINKAGE CGKeymasterTent : public CGKeys +// class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject + +// POSSIBLE +// class DLL_LINKAGE CGSignBottle : public CGObjectInstance //signs and ocean bottles +// class DLL_LINKAGE CGWitchHut : public CPlayersVisited +// class DLL_LINKAGE CGScholar : public CGObjectInstance diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp new file mode 100644 index 000000000..14f45fd9c --- /dev/null +++ b/lib/mapObjects/CommonConstructors.cpp @@ -0,0 +1,386 @@ +#include "StdInc.h" +#include "CommonConstructors.h" + +#include "CGTownInstance.h" +#include "CGHeroInstance.h" +#include "CBank.h" +#include "../mapping/CMap.h" +#include "../CHeroHandler.h" +#include "../CCreatureHandler.h" +#include "JsonRandom.h" + +/* + * CommonConstructors.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +CObstacleConstructor::CObstacleConstructor() +{ +} + +bool CObstacleConstructor::isStaticObject() +{ + return true; +} + +CTownInstanceConstructor::CTownInstanceConstructor(): + faction(nullptr) +{ +} + +void CTownInstanceConstructor::initTypeData(const JsonNode & input) +{ + VLC->modh->identifiers.requestIdentifier("faction", input["faction"], [&](si32 index) + { + faction = VLC->townh->factions[index]; + }); + + filtersJson = input["filters"]; +} + +void CTownInstanceConstructor::afterLoadFinalization() +{ + assert(faction); + for (auto entry : filtersJson.Struct()) + { + filters[entry.first] = LogicalExpression(entry.second, [this](const JsonNode & node) + { + return BuildingID(VLC->modh->identifiers.getIdentifier("building." + faction->identifier, node.Vector()[0]).get()); + }); + } +} + +bool CTownInstanceConstructor::objectFilter(const CGObjectInstance * object, const ObjectTemplate & templ) const +{ + auto town = dynamic_cast(object); + + auto buildTest = [&](const BuildingID & id) + { + return town->hasBuilt(id); + }; + + if (filters.count(templ.stringID)) + return filters.at(templ.stringID).test(buildTest); + return false; +} + +CGObjectInstance * CTownInstanceConstructor::create(ObjectTemplate tmpl) const +{ + CGTownInstance * obj = createTyped(tmpl); + obj->town = faction->town; + obj->tempOwner = PlayerColor::NEUTRAL; + return obj; +} + +void CTownInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const +{ + auto templ = getOverride(object->cb->getTile(object->pos)->terType, object); + if (templ) + object->appearance = templ.get(); +} + +CHeroInstanceConstructor::CHeroInstanceConstructor() +{ + +} + +void CHeroInstanceConstructor::initTypeData(const JsonNode & input) +{ + VLC->modh->identifiers.requestIdentifier("heroClass", input["heroClass"], + [&](si32 index) { heroClass = VLC->heroh->classes.heroClasses[index]; }); + + filtersJson = input["filters"]; +} + +void CHeroInstanceConstructor::afterLoadFinalization() +{ + for (auto entry : filtersJson.Struct()) + { + filters[entry.first] = LogicalExpression(entry.second, [this](const JsonNode & node) + { + return HeroTypeID(VLC->modh->identifiers.getIdentifier("hero", node.Vector()[0]).get()); + }); + } +} + +bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, const ObjectTemplate & templ) const +{ + auto hero = dynamic_cast(object); + + auto heroTest = [&](const HeroTypeID & id) + { + return hero->type->ID == id; + }; + + if (filters.count(templ.stringID)) + { + return filters.at(templ.stringID).test(heroTest); + } + return false; +} + +CGObjectInstance * CHeroInstanceConstructor::create(ObjectTemplate tmpl) const +{ + CGHeroInstance * obj = createTyped(tmpl); + obj->type = nullptr; //FIXME: set to valid value. somehow. + return obj; +} + +void CHeroInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const +{ + +} + +CDwellingInstanceConstructor::CDwellingInstanceConstructor() +{ + +} + +void CDwellingInstanceConstructor::initTypeData(const JsonNode & input) +{ + const JsonVector & levels = input["creatures"].Vector(); + availableCreatures.resize(levels.size()); + for (size_t i=0; imodh->identifiers.requestIdentifier("creature", creatures[j], [=] (si32 index) + { + availableCreatures[i][j] = VLC->creh->creatures[index]; + }); + } + } + guards = input["guards"]; +} + +bool CDwellingInstanceConstructor::objectFilter(const CGObjectInstance *, const ObjectTemplate &) const +{ + return false; +} + +CGObjectInstance * CDwellingInstanceConstructor::create(ObjectTemplate tmpl) const +{ + CGDwelling * obj = createTyped(tmpl); + + obj->creatures.resize(availableCreatures.size()); + for (auto & entry : availableCreatures) + { + for (const CCreature * cre : entry) + obj->creatures.back().second.push_back(cre->idNumber); + } + return obj; +} + +void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator &rng) const +{ + CGDwelling * dwelling = dynamic_cast(object); + + dwelling->creatures.clear(); + dwelling->creatures.resize(availableCreatures.size()); + + for (auto & entry : availableCreatures) + { + for (const CCreature * cre : entry) + dwelling->creatures.back().second.push_back(cre->idNumber); + } + + if (guards.getType() == JsonNode::DATA_BOOL) + { + const CCreature * crea = availableCreatures.at(0).at(0); + dwelling->putStack(SlotID(0), new CStackInstance(crea->idNumber, crea->growth * 3 )); + } + else for (auto & stack : JsonRandom::loadCreatures(guards, rng)) + { + dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.type->idNumber, stack.count)); + } +} + +bool CDwellingInstanceConstructor::producesCreature(const CCreature * crea) const +{ + for (auto & entry : availableCreatures) + { + for (const CCreature * cre : entry) + if (crea == cre) + return true; + } + return false; +} + +CBankInstanceConstructor::CBankInstanceConstructor() +{ +} + +void CBankInstanceConstructor::initTypeData(const JsonNode & input) +{ + //TODO: name = input["name"].String(); + levels = input["levels"].Vector(); + bankResetDuration = input["resetDuration"].Float(); +} + +CGObjectInstance *CBankInstanceConstructor::create(ObjectTemplate tmpl) const +{ + return createTyped(tmpl); +} + +BankConfig CBankInstanceConstructor::generateConfig(const JsonNode & level, CRandomGenerator & rng) const +{ + BankConfig bc; + + bc.chance = level["chance"].Float(); + + bc.guards = JsonRandom::loadCreatures(level["guards"], rng); + bc.upgradeChance = level["upgrade_chance"].Float(); + bc.combatValue = level["combat_value"].Float(); + + std::vector spells; + for (size_t i=0; i<6; i++) + IObjectInterface::cb->getAllowedSpells(spells, i); + + bc.resources = Res::ResourceSet(level["reward"]["resources"]); + bc.creatures = JsonRandom::loadCreatures(level["reward"]["creatures"], rng); + bc.artifacts = JsonRandom::loadArtifacts(level["reward"]["artifacts"], rng); + bc.spells = JsonRandom::loadSpells(level["reward"]["spells"], rng, spells); + + bc.value = level["value"].Float(); + + return bc; +} + +void CBankInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const +{ + auto bank = dynamic_cast(object); + + bank->resetDuration = bankResetDuration; + + si32 totalChance = 0; + for (auto & node : levels) + totalChance += node["chance"].Float(); + + assert(totalChance != 0); + + si32 selectedChance = rng.nextInt(totalChance - 1); + + for (auto & node : levels) + { + if (selectedChance < node["chance"].Float()) + { + bank->setConfig(generateConfig(node, rng)); + } + else + { + selectedChance -= node["chance"].Float(); + } + + } +} + +CBankInfo::CBankInfo(JsonVector config): + config(config) +{ +} + +static void addStackToArmy(IObjectInfo::CArmyStructure & army, const CCreature * crea, si32 amount) +{ + army.totalStrength += crea->fightValue * amount; + + bool walker = true; + if (crea->hasBonusOfType(Bonus::SHOOTER)) + { + army.shootersStrength += crea->fightValue * amount; + walker = false; + } + if (crea->hasBonusOfType(Bonus::FLYING)) + { + army.flyersStrength += crea->fightValue * amount; + walker = false; + } + if (walker) + army.walkersStrength += crea->fightValue * amount; +} + +IObjectInfo::CArmyStructure CBankInfo::minGuards() const +{ + std::vector armies; + for (auto configEntry : config) + { + auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]); + IObjectInfo::CArmyStructure army; + for (auto & stack : stacks) + { + assert(!stack.allowedCreatures.empty()); + auto weakest = boost::range::min_element(stack.allowedCreatures, [](const CCreature * a, const CCreature * b) + { + return a->fightValue < b->fightValue; + }); + addStackToArmy(army, *weakest, stack.minAmount); + } + armies.push_back(army); + } + return *boost::range::min_element(armies); +} + +IObjectInfo::CArmyStructure CBankInfo::maxGuards() const +{ + std::vector armies; + for (auto configEntry : config) + { + auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]); + IObjectInfo::CArmyStructure army; + for (auto & stack : stacks) + { + assert(!stack.allowedCreatures.empty()); + auto strongest = boost::range::max_element(stack.allowedCreatures, [](const CCreature * a, const CCreature * b) + { + return a->fightValue < b->fightValue; + }); + addStackToArmy(army, *strongest, stack.maxAmount); + } + armies.push_back(army); + } + return *boost::range::max_element(armies); +} + +bool CBankInfo::givesResources() const +{ + for (const JsonNode & node : config) + if (!node["reward"]["resources"].isNull()) + return true; + return false; +} + +bool CBankInfo::givesArtifacts() const +{ + for (const JsonNode & node : config) + if (!node["reward"]["artifacts"].isNull()) + return true; + return false; +} + +bool CBankInfo::givesCreatures() const +{ + for (const JsonNode & node : config) + if (!node["reward"]["creatures"].isNull()) + return true; + return false; +} + +bool CBankInfo::givesSpells() const +{ + for (const JsonNode & node : config) + if (!node["reward"]["spells"].isNull()) + return true; + return false; +} + + +std::unique_ptr CBankInstanceConstructor::getObjectInfo(ObjectTemplate tmpl) const +{ + return std::unique_ptr(new CBankInfo(levels)); +} diff --git a/lib/mapObjects/CommonConstructors.h b/lib/mapObjects/CommonConstructors.h new file mode 100644 index 000000000..241673d12 --- /dev/null +++ b/lib/mapObjects/CommonConstructors.h @@ -0,0 +1,185 @@ +#pragma once + +#include "CObjectClassesHandler.h" +#include "../CTownHandler.h" // for building ID-based filters + +/* + * CommonConstructors.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 + * + */ + +class CGTownInstance; +class CGHeroInstance; +class CGDwelling; +//class CGArtifact; +//class CGCreature; +class CHeroClass; +class CBank; +class CStackBasicDescriptor; + +/// Class that is used for objects that do not have dedicated handler +template +class CDefaultObjectTypeHandler : public AObjectTypeHandler +{ +protected: + ObjectType * createTyped(ObjectTemplate tmpl) const + { + auto obj = new ObjectType(); + obj->ID = tmpl.id; + obj->subID = tmpl.subid; + obj->appearance = tmpl; + return obj; + } +public: + CDefaultObjectTypeHandler(){} + + CGObjectInstance * create(ObjectTemplate tmpl) const + { + return createTyped(tmpl); + } + + virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const + { + } + + virtual std::unique_ptr getObjectInfo(ObjectTemplate tmpl) const + { + return nullptr; + } +}; + +class CObstacleConstructor : public CDefaultObjectTypeHandler +{ +public: + CObstacleConstructor(); + bool isStaticObject(); +}; + +class CTownInstanceConstructor : public CDefaultObjectTypeHandler +{ + JsonNode filtersJson; +protected: + bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; + void initTypeData(const JsonNode & input); + +public: + CFaction * faction; + std::map> filters; + + CTownInstanceConstructor(); + CGObjectInstance * create(ObjectTemplate tmpl) const; + void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const; + void afterLoadFinalization(); + + template void serialize(Handler &h, const int version) + { + h & filtersJson & faction & filters; + h & static_cast&>(*this); + } +}; + +class CHeroInstanceConstructor : public CDefaultObjectTypeHandler +{ + JsonNode filtersJson; +protected: + bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; + void initTypeData(const JsonNode & input); + +public: + CHeroClass * heroClass; + std::map> filters; + + CHeroInstanceConstructor(); + CGObjectInstance * create(ObjectTemplate tmpl) const; + void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const; + void afterLoadFinalization(); + + template void serialize(Handler &h, const int version) + { + h & filtersJson & heroClass & filters; + h & static_cast&>(*this); + } +}; + +class CDwellingInstanceConstructor : public CDefaultObjectTypeHandler +{ + std::vector> availableCreatures; + + JsonNode guards; + +protected: + bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; + void initTypeData(const JsonNode & input); + +public: + + CDwellingInstanceConstructor(); + CGObjectInstance * create(ObjectTemplate tmpl) const; + void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const; + + bool producesCreature(const CCreature * crea) const; + + template void serialize(Handler &h, const int version) + { + h & availableCreatures & guards; + h & static_cast&>(*this); + } +}; + +struct BankConfig +{ + BankConfig() { chance = upgradeChance = combatValue = value = 0; }; + ui32 value; //overall value of given things + ui32 chance; //chance for this level being chosen + ui32 upgradeChance; //chance for creatures to be in upgraded versions + ui32 combatValue; //how hard are guards of this level + std::vector guards; //creature ID, amount + Res::ResourceSet resources; //resources given in case of victory + std::vector creatures; //creatures granted in case of victory (creature ID, amount) + std::vector artifacts; //artifacts given in case of victory + std::vector spells; // granted spell(s), for Pyramid + + template void serialize(Handler &h, const int version) + { + h & chance & upgradeChance & guards & combatValue & resources & creatures & artifacts & value & spells; + } +}; + +class CBankInfo : public IObjectInfo +{ + JsonVector config; +public: + CBankInfo(JsonVector config); + + CArmyStructure minGuards() const; + CArmyStructure maxGuards() const; + bool givesResources() const; + bool givesArtifacts() const; + bool givesCreatures() const; + bool givesSpells() const; +}; + +class CBankInstanceConstructor : public CDefaultObjectTypeHandler +{ + BankConfig generateConfig(const JsonNode & conf, CRandomGenerator & rng) const; + + JsonVector levels; +protected: + void initTypeData(const JsonNode & input); + +public: + // all banks of this type will be reset N days after clearing, + si32 bankResetDuration; + + CBankInstanceConstructor(); + + CGObjectInstance *create(ObjectTemplate tmpl) const; + void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const; + + std::unique_ptr getObjectInfo(ObjectTemplate tmpl) const; +}; diff --git a/lib/mapObjects/JsonRandom.cpp b/lib/mapObjects/JsonRandom.cpp new file mode 100644 index 000000000..e57b2483e --- /dev/null +++ b/lib/mapObjects/JsonRandom.cpp @@ -0,0 +1,225 @@ +/* + * + * CRewardableObject.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "JsonRandom.h" + +#include "../JsonNode.h" +#include "../CRandomGenerator.h" +#include "../StringConstants.h" +#include "../VCMI_Lib.h" +#include "../CModHandler.h" +#include "../CArtHandler.h" +#include "../CCreatureHandler.h" +#include "../CCreatureSet.h" +#include "../CSpellHandler.h" + +namespace JsonRandom +{ + si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue) + { + if (value.isNull()) + return defaultValue; + if (value.getType() == JsonNode::DATA_FLOAT) + return value.Float(); + if (!value["amount"].isNull()) + return value["amount"].Float(); + si32 min = value["min"].Float(); + si32 max = value["max"].Float(); + return rng.getIntRange(min, max)(); + } + + TResources loadResources(const JsonNode & value, CRandomGenerator & rng) + { + TResources ret; + for (size_t i=0; i loadPrimary(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for (auto & name : PrimarySkill::names) + { + ret.push_back(loadValue(value[name], rng)); + } + return ret; + } + + std::map loadSecondary(const JsonNode & value, CRandomGenerator & rng) + { + std::map ret; + for (auto & pair : value.Struct()) + { + SecondarySkill id(VLC->modh->identifiers.getIdentifier(pair.second.meta, "skill", pair.first).get()); + ret[id] = loadValue(pair.second, rng); + } + return ret; + } + + ArtifactID loadArtifact(const JsonNode & value, CRandomGenerator & rng) + { + if (value.getType() == JsonNode::DATA_STRING) + return ArtifactID(VLC->modh->identifiers.getIdentifier("artifact", value).get()); + + std::set allowedClasses; + std::set allowedPositions; + ui32 minValue = 0; + ui32 maxValue = std::numeric_limits::max(); + + if (value["class"].getType() == JsonNode::DATA_STRING) + allowedClasses.insert(VLC->arth->stringToClass(value["class"].String())); + else + for (auto & entry : value["class"].Vector()) + allowedClasses.insert(VLC->arth->stringToClass(entry.String())); + + if (value["slot"].getType() == JsonNode::DATA_STRING) + allowedPositions.insert(VLC->arth->stringToSlot(value["class"].String())); + else + for (auto & entry : value["slot"].Vector()) + allowedPositions.insert(VLC->arth->stringToSlot(entry.String())); + + if (!value["minValue"].isNull()) minValue = value["minValue"].Float(); + if (!value["maxValue"].isNull()) maxValue = value["maxValue"].Float(); + + return VLC->arth->pickRandomArtifact(rng, [=](ArtifactID artID) -> bool + { + CArtifact * art = VLC->arth->artifacts[artID]; + + if (!vstd::iswithin(art->price, minValue, maxValue)) + return false; + + if (!allowedClasses.empty() && !allowedClasses.count(art->aClass)) + return false; + + if (!allowedPositions.empty()) + { + for (auto pos : art->possibleSlots[ArtBearer::HERO]) + { + if (allowedPositions.count(pos)) + return true; + } + return false; + } + return true; + }); + } + + std::vector loadArtifacts(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for (const JsonNode & entry : value.Vector()) + { + ret.push_back(loadArtifact(entry, rng)); + } + return ret; + } + + SpellID loadSpell(const JsonNode & value, CRandomGenerator & rng, std::vector spells) + { + if (value.getType() == JsonNode::DATA_STRING) + return SpellID(VLC->modh->identifiers.getIdentifier("spell", value).get()); + if (value["type"].getType() == JsonNode::DATA_STRING) + return SpellID(VLC->modh->identifiers.getIdentifier("spell", value["type"]).get()); + + spells.erase(std::remove_if(spells.begin(), spells.end(), [=](SpellID spell) + { + return VLC->spellh->objects[spell]->level != si32(value["level"].Float()); + }), spells.end()); + + return SpellID(*RandomGeneratorUtil::nextItem(spells, rng)); + } + + std::vector loadSpells(const JsonNode & value, CRandomGenerator & rng, std::vector spells) + { + // possible extensions: (taken from spell json config) + // "type": "adventure",//"adventure", "combat", "ability" + // "school": {"air":true, "earth":true, "fire":true, "water":true}, + // "level": 1, + + std::vector ret; + for (const JsonNode & entry : value.Vector()) + { + ret.push_back(loadSpell(entry, rng, spells)); + } + return ret; + } + + CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng) + { + CStackBasicDescriptor stack; + stack.type = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier("creature", value["type"]).get()]; + stack.count = loadValue(value, rng); + if (!value["upgradeChance"].isNull() && !stack.type->upgrades.empty()) + { + if (int(value["upgradeChance"].Float()) > rng.nextInt(99)) // select random upgrade + { + stack.type = VLC->creh->creatures[*RandomGeneratorUtil::nextItem(stack.type->upgrades, rng)]; + } + } + return stack; + } + + std::vector loadCreatures(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for (const JsonNode & node : value.Vector()) + { + ret.push_back(loadCreature(node, rng)); + } + return ret; + } + + std::vector evaluateCreatures(const JsonNode & value) + { + std::vector ret; + for (const JsonNode & node : value.Vector()) + { + RandomStackInfo info; + + if (!node["amount"].isNull()) + info.minAmount = info.maxAmount = node["amount"].Float(); + else + { + info.minAmount = node["min"].Float(); + info.maxAmount = node["max"].Float(); + } + const CCreature * crea = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier("creature", node["type"]).get()]; + info.allowedCreatures.push_back(crea); + if (!node["upgradeChance"].Float() > 0) + { + for (auto creaID : crea->upgrades) + info.allowedCreatures.push_back(VLC->creh->creatures[creaID]); + } + } + return ret; + } + + std::vector loadBonuses(const JsonNode & value) + { + std::vector ret; + for (const JsonNode & entry : value.Vector()) + { + Bonus * bonus = JsonUtils::parseBonus(entry); + ret.push_back(*bonus); + delete bonus; + } + return ret; + } + + std::vector loadComponents(const JsonNode & value) + { + //TODO + } +} diff --git a/lib/mapObjects/JsonRandom.h b/lib/mapObjects/JsonRandom.h new file mode 100644 index 000000000..80feeff20 --- /dev/null +++ b/lib/mapObjects/JsonRandom.h @@ -0,0 +1,50 @@ +#pragma once + +#include "../GameConstants.h" +#include "../ResourceSet.h" + +/* + * JsonRandom.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 + * + */ + +class JsonNode; +typedef std::vector JsonVector; +class CRandomGenerator; + +class Bonus; +class Component; +class CStackBasicDescriptor; + +namespace JsonRandom +{ + struct DLL_LINKAGE RandomStackInfo + { + std::vector allowedCreatures; + si32 minAmount; + si32 maxAmount; + }; + + DLL_LINKAGE si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue = 0); + DLL_LINKAGE TResources loadResources(const JsonNode & value, CRandomGenerator & rng); + DLL_LINKAGE std::vector loadPrimary(const JsonNode & value, CRandomGenerator & rng); + DLL_LINKAGE std::map loadSecondary(const JsonNode & value, CRandomGenerator & rng); + + DLL_LINKAGE ArtifactID loadArtifact(const JsonNode & value, CRandomGenerator & rng); + DLL_LINKAGE std::vector loadArtifacts(const JsonNode & value, CRandomGenerator & rng); + + DLL_LINKAGE SpellID loadSpell(const JsonNode & value, CRandomGenerator & rng, std::vector spells); + DLL_LINKAGE std::vector loadSpells(const JsonNode & value, CRandomGenerator & rng, std::vector spells); + + DLL_LINKAGE CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng); + DLL_LINKAGE std::vector loadCreatures(const JsonNode & value, CRandomGenerator & rng); + DLL_LINKAGE std::vector evaluateCreatures(const JsonNode & value); + + DLL_LINKAGE std::vector loadBonuses(const JsonNode & value); + DLL_LINKAGE std::vector loadComponents(const JsonNode & value); +} diff --git a/lib/mapObjects/MapObjects.h b/lib/mapObjects/MapObjects.h new file mode 100644 index 000000000..44eeb4495 --- /dev/null +++ b/lib/mapObjects/MapObjects.h @@ -0,0 +1,25 @@ +#pragma once + +/* + * MapObjects.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 + * + */ + +// Helper header that includes all map objects, similar to old CObjectHandler.h +// Possible TODO - remove this header after CObjectHandler.cpp will be fully split into smaller files +#include "CObjectHandler.h" + +#include "CArmedInstance.h" +#include "CBank.h" +#include "CGHeroInstance.h" +#include "CGMarket.h" +#include "CGTownInstance.h" +#include "CGPandoraBox.h" +#include "CRewardableObject.h" +#include "MiscObjects.h" +#include "CQuest.h" diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp new file mode 100644 index 000000000..4fbfade1b --- /dev/null +++ b/lib/mapObjects/MiscObjects.cpp @@ -0,0 +1,1573 @@ +/* + * MiscObjects.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "MiscObjects.h" + +#include "../NetPacks.h" +#include "../CGeneralTextHandler.h" +#include "../CSoundBase.h" + +#include "CObjectClassesHandler.h" +#include "../CSpellHandler.h" + +using namespace boost::assign; + +std::map > > CGTeleport::objs; +std::vector > CGTeleport::gates; +std::map > CGMagi::eyelist; +ui8 CGObelisk::obeliskCount; //how many obelisks are on map +std::map CGObelisk::visited; //map: team_id => how many obelisks has been visited + +///helpers +static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1) +{ + OpenWindow ow; + ow.window = type; + ow.id1 = id1; + ow.id2 = id2; + IObjectInterface::cb->sendAndApply(&ow); +} + +static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) +{ + InfoWindow iw; + iw.soundID = soundID; + iw.player = playerID; + iw.text.addTxt(MetaString::ADVOB_TXT,txtID); + IObjectInterface::cb->sendAndApply(&iw); +} + +static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID) +{ + const PlayerColor playerID = h->getOwner(); + showInfoDialog(playerID,txtID,soundID); +} + +static std::string & visitedTxt(const bool visited) +{ + int id = visited ? 352 : 353; + return VLC->generaltexth->allTexts[id]; +} + +void CPlayersVisited::setPropertyDer( ui8 what, ui32 val ) +{ + if(what == 10) + players.insert(PlayerColor(val)); +} + +bool CPlayersVisited::wasVisited( PlayerColor player ) const +{ + return vstd::contains(players,player); +} + +bool CPlayersVisited::wasVisited( TeamID team ) const +{ + for(auto i : players) + { + if(cb->getPlayer(i)->team == team) + return true; + } + return false; +} + +const std::string & CGCreature::getHoverText() const +{ + if(stacks.empty()) + { + static const std::string errorValue("!!!INVALID_STACK!!!"); + + //should not happen... + logGlobal->errorStream() << "Invalid stack at tile " << pos << ": subID=" << subID << "; id=" << id; + return errorValue; // references to temporary are illegal - use pre-constructed string + } + + MetaString ms; + int pom = stacks.begin()->second->getQuantityID(); + pom = 172 + 3*pom; + ms.addTxt(MetaString::ARRAY_TXT,pom); + ms << " " ; + ms.addTxt(MetaString::CRE_PL_NAMES,subID); + ms.toString(hoverName); + + if(const CGHeroInstance *selHero = cb->getSelectedHero(cb->getCurrentPlayer())) + { + const JsonNode & texts = VLC->generaltexth->localizedTexts["adventureMap"]["monsterThreat"]; + + hoverName += texts["title"].String(); + int choice; + double ratio = ((double)getArmyStrength() / selHero->getTotalStrength()); + if (ratio < 0.1) choice = 0; + else if (ratio < 0.25) choice = 1; + else if (ratio < 0.6) choice = 2; + else if (ratio < 0.9) choice = 3; + else if (ratio < 1.1) choice = 4; + else if (ratio < 1.3) choice = 5; + else if (ratio < 1.8) choice = 6; + else if (ratio < 2.5) choice = 7; + else if (ratio < 4) choice = 8; + else if (ratio < 8) choice = 9; + else if (ratio < 20) choice = 10; + else choice = 11; + hoverName += texts["levels"].Vector()[choice].String(); + } + return hoverName; +} +void CGCreature::onHeroVisit( const CGHeroInstance * h ) const +{ + int action = takenAction(h); + switch( action ) //decide what we do... + { + case FIGHT: + fight(h); + break; + case FLEE: //flee + { + flee(h); + break; + } + case JOIN_FOR_FREE: //join for free + { + BlockingDialog ynd(true,false); + ynd.player = h->tempOwner; + ynd.text.addTxt(MetaString::ADVOB_TXT, 86); + ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID); + cb->showBlockingDialog(&ynd); + break; + } + default: //join for gold + { + assert(action > 0); + + //ask if player agrees to pay gold + BlockingDialog ynd(true,false); + ynd.player = h->tempOwner; + std::string tmp = VLC->generaltexth->advobtxt[90]; + boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast(getStackCount(SlotID(0)))); + boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast(action)); + boost::algorithm::replace_first(tmp,"%s",VLC->creh->creatures[subID]->namePl); + ynd.text << tmp; + cb->showBlockingDialog(&ynd); + break; + } + } + +} + +void CGCreature::initObj() +{ + blockVisit = true; + switch(character) + { + case 0: + character = -4; + break; + case 1: + character = cb->gameState()->getRandomGenerator().nextInt(1, 7); + break; + case 2: + character = cb->gameState()->getRandomGenerator().nextInt(1, 10); + break; + case 3: + character = cb->gameState()->getRandomGenerator().nextInt(4, 10); + break; + case 4: + character = 10; + break; + } + + stacks[SlotID(0)]->setType(CreatureID(subID)); + TQuantity &amount = stacks[SlotID(0)]->count; + CCreature &c = *VLC->creh->creatures[subID]; + if(amount == 0) + { + amount = cb->gameState()->getRandomGenerator().nextInt(c.ammMin, c.ammMax); + + if(amount == 0) //armies with 0 creatures are illegal + { + logGlobal->warnStream() << "Problem: stack " << nodeName() << " cannot have 0 creatures. Check properties of " << c.nodeName(); + amount = 1; + } + } + formation.randomFormation = cb->gameState()->getRandomGenerator().nextInt(); + + temppower = stacks[SlotID(0)]->count * 1000; + refusedJoining = false; +} + +void CGCreature::newTurn() const +{//Works only for stacks of single type of size up to 2 millions + if (stacks.begin()->second->count < VLC->modh->settings.CREEP_SIZE && cb->getDate(Date::DAY_OF_WEEK) == 1 && cb->getDate(Date::DAY) > 1) + { + ui32 power = temppower * (100 + VLC->modh->settings.WEEKLY_GROWTH)/100; + cb->setObjProperty(id, ObjProperty::MONSTER_COUNT, std::min (power/1000 , (ui32)VLC->modh->settings.CREEP_SIZE)); //set new amount + cb->setObjProperty(id, ObjProperty::MONSTER_POWER, power); //increase temppower + } + if (VLC->modh->modules.STACK_EXP) + cb->setObjProperty(id, ObjProperty::MONSTER_EXP, VLC->modh->settings.NEUTRAL_STACK_EXP); //for testing purpose +} +void CGCreature::setPropertyDer(ui8 what, ui32 val) +{ + switch (what) + { + case ObjProperty::MONSTER_COUNT: + stacks[SlotID(0)]->count = val; + break; + case ObjProperty::MONSTER_POWER: + temppower = val; + break; + case ObjProperty::MONSTER_EXP: + giveStackExp(val); + break; + case ObjProperty::MONSTER_RESTORE_TYPE: + formation.basicType = val; + break; + case ObjProperty::MONSTER_REFUSED_JOIN: + refusedJoining = val; + break; + } +} + +int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const +{ + //calculate relative strength of hero and creatures armies + double relStrength = double(h->getTotalStrength()) / getArmyStrength(); + + int powerFactor; + if(relStrength >= 7) + powerFactor = 11; + + else if(relStrength >= 1) + powerFactor = (int)(2*(relStrength-1)); + + else if(relStrength >= 0.5) + powerFactor = -1; + + else if(relStrength >= 0.333) + powerFactor = -2; + else + powerFactor = -3; + + std::set myKindCres; //what creatures are the same kind as we + const CCreature * myCreature = VLC->creh->creatures[subID]; + myKindCres.insert(myCreature->idNumber); //we + myKindCres.insert(myCreature->upgrades.begin(), myCreature->upgrades.end()); //our upgrades + + for(ConstTransitivePtr &crea : VLC->creh->creatures) + { + if(vstd::contains(crea->upgrades, myCreature->idNumber)) //it's our base creatures + myKindCres.insert(crea->idNumber); + } + + int count = 0, //how many creatures of similar kind has hero + totalCount = 0; + + for (auto & elem : h->Slots()) + { + if(vstd::contains(myKindCres,elem.second->type->idNumber)) + count += elem.second->count; + totalCount += elem.second->count; + } + + int sympathy = 0; // 0 if hero have no similar creatures + if(count) + sympathy++; // 1 - if hero have at least 1 similar creature + if(count*2 > totalCount) + sympathy++; // 2 - hero have similar creatures more that 50% + + int charisma = powerFactor + h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy; + + if(charisma < character) //creatures will fight + return -2; + + if (allowJoin) + { + if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy + 1 >= character) + return 0; //join for free + + else if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) * 2 + sympathy + 1 >= character) + return VLC->creh->creatures[subID]->cost[6] * getStackCount(SlotID(0)); //join for gold + } + + //we are still here - creatures have not joined hero, flee or fight + + if (charisma > character) + return -1; //flee + else + return -2; //fight +} + +void CGCreature::fleeDecision(const CGHeroInstance *h, ui32 pursue) const +{ + if(refusedJoining) + cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, false); + + if(pursue) + { + fight(h); + } + else + { + cb->removeObject(this); + } +} + +void CGCreature::joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const +{ + if(!accept) + { + if(takenAction(h,false) == -1) //they flee + { + cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, true); + flee(h); + } + else //they fight + { + showInfoDialog(h,87,0);//Insulted by your refusal of their offer, the monsters attack! + fight(h); + } + } + else //accepted + { + if (cb->getResource(h->tempOwner, Res::GOLD) < cost) //player don't have enough gold! + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.text << std::pair(1,29); //You don't have enough gold + cb->showInfoDialog(&iw); + + //act as if player refused + joinDecision(h,cost,false); + return; + } + + //take gold + if(cost) + cb->giveResource(h->tempOwner,Res::GOLD,-cost); + + cb->tryJoiningArmy(this, h, true, true); + } +} + +void CGCreature::fight( const CGHeroInstance *h ) const +{ + //split stacks + //TODO: multiple creature types in a stack? + int basicType = stacks.begin()->second->type->idNumber; + cb->setObjProperty(id, ObjProperty::MONSTER_RESTORE_TYPE, basicType); //store info about creature stack + + double relativePower = static_cast(h->getTotalStrength()) / getArmyStrength(); + int stacksCount; + //TODO: number depends on tile type + if (relativePower < 0.5) + { + stacksCount = 7; + } + else if (relativePower < 0.67) + { + stacksCount = 7; + } + else if (relativePower < 1) + { + stacksCount = 6; + } + else if (relativePower < 1.5) + { + stacksCount = 5; + } + else if (relativePower < 2) + { + stacksCount = 4; + } + else + { + stacksCount = 3; + } + SlotID sourceSlot = stacks.begin()->first; + SlotID destSlot; + for (int stacksLeft = stacksCount; stacksLeft > 1; --stacksLeft) + { + int stackSize = stacks.begin()->second->count / stacksLeft; + if (stackSize) + { + if ((destSlot = getFreeSlot()).validSlot()) + cb->moveStack(StackLocation(this, sourceSlot), StackLocation(this, destSlot), stackSize); + else + { + logGlobal->warnStream() <<"Warning! Not enough empty slots to split stack!"; + break; + } + } + else break; + } + if (stacksCount > 1) + { + if (formation.randomFormation % 100 < 50) //upgrade + { + SlotID slotId = SlotID(stacks.size() / 2); + const auto & upgrades = getStack(slotId).type->upgrades; + if(!upgrades.empty()) + { + auto it = RandomGeneratorUtil::nextItem(upgrades, cb->gameState()->getRandomGenerator()); + cb->changeStackType(StackLocation(this, slotId), VLC->creh->creatures[*it]); + } + } + } + + cb->startBattleI(h, this); + +} + +void CGCreature::flee( const CGHeroInstance * h ) const +{ + BlockingDialog ynd(true,false); + ynd.player = h->tempOwner; + ynd.text.addTxt(MetaString::ADVOB_TXT,91); + ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID); + cb->showBlockingDialog(&ynd); +} + +void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + + if(result.winner==0) + { + cb->removeObject(this); + } + else + { + //int killedAmount=0; + //for(std::set >::iterator i=result->casualties[1].begin(); i!=result->casualties[1].end(); i++) + // if(i->first == subID) + // killedAmount += i->second; + //cb->setAmount(id, slots.find(0)->second.second - killedAmount); + + /* + MetaString ms; + int pom = slots.find(0)->second.getQuantityID(); + pom = 174 + 3*pom + 1; + ms << std::pair(6,pom) << " " << std::pair(7,subID); + cb->setHoverName(id,&ms); + cb->setObjProperty(id, 11, slots.begin()->second.count * 1000); + */ + + //merge stacks into one + TSlots::const_iterator i; + CCreature * cre = VLC->creh->creatures[formation.basicType]; + for (i = stacks.begin(); i != stacks.end(); i++) + { + if (cre->isMyUpgrade(i->second->type)) + { + cb->changeStackType (StackLocation(this, i->first), cre); //un-upgrade creatures + } + } + + //first stack has to be at slot 0 -> if original one got killed, move there first remaining stack + if(!hasStackAtSlot(SlotID(0))) + cb->moveStack(StackLocation(this, stacks.begin()->first), StackLocation(this, SlotID(0)), stacks.begin()->second->count); + + while (stacks.size() > 1) //hopefully that's enough + { + // TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere) + i = stacks.end(); + i--; + SlotID slot = getSlotFor(i->second->type); + if (slot == i->first) //no reason to move stack to its own slot + break; + else + cb->moveStack (StackLocation(this, i->first), StackLocation(this, slot), i->second->count); + } + + cb->setObjProperty(id, ObjProperty::MONSTER_POWER, stacks.begin()->second->count * 1000); //remember casualties + } +} + +void CGCreature::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + auto action = takenAction(hero); + if(!refusedJoining && action >= JOIN_FOR_FREE) //higher means price + joinDecision(hero, action, answer); + else if(action != FIGHT) + fleeDecision(hero, answer); + else + assert(0); +} + +void CGMine::onHeroVisit( const CGHeroInstance * h ) const +{ + int relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); + + if(relations == 2) //we're visiting our mine + { + cb->showGarrisonDialog(id,h->id,true); + return; + } + else if (relations == 1)//ally + return; + + if(stacksCount()) //Mine is guarded + { + BlockingDialog ynd(true,false); + ynd.player = h->tempOwner; + ynd.text.addTxt(MetaString::ADVOB_TXT, subID == 7 ? 84 : 187); + cb->showBlockingDialog(&ynd); + return; + } + + flagMine(h->tempOwner); + +} + +void CGMine::newTurn() const +{ + if(cb->getDate() == 1) + return; + + if (tempOwner == PlayerColor::NEUTRAL) + return; + + cb->giveResource(tempOwner, producedResource, producedQuantity); +} + +void CGMine::initObj() +{ + if(subID >= 7) //Abandoned Mine + { + //set guardians + int howManyTroglodytes = cb->gameState()->getRandomGenerator().nextInt(100, 199); + auto troglodytes = new CStackInstance(CreatureID::TROGLODYTES, howManyTroglodytes); + putStack(SlotID(0), troglodytes); + + //after map reading tempOwner placeholds bitmask for allowed resources + std::vector possibleResources; + for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) + if(tempOwner.getNum() & 1<(i)); + + assert(!possibleResources.empty()); + producedResource = *RandomGeneratorUtil::nextItem(possibleResources, cb->gameState()->getRandomGenerator()); + tempOwner = PlayerColor::NEUTRAL; + hoverName = VLC->generaltexth->mines[7].first + "\n" + VLC->generaltexth->allTexts[202] + " " + troglodytes->getQuantityTXT(false) + " " + troglodytes->type->namePl; + } + else + { + producedResource = static_cast(subID); + + MetaString ms; + ms << std::pair(9,producedResource); + if(tempOwner >= PlayerColor::PLAYER_LIMIT) + tempOwner = PlayerColor::NEUTRAL; + else + ms << " (" << std::pair(6,23+tempOwner.getNum()) << ")"; + ms.toString(hoverName); + } + + producedQuantity = defaultResProduction(); +} + +void CGMine::flagMine(PlayerColor player) const +{ + assert(tempOwner != player); + cb->setOwner(this, player); //not ours? flag it! + + MetaString ms; + ms << std::pair(9,subID) << "\n(" << std::pair(6,23+player.getNum()) << ")"; + if(subID == 7) + { + ms << "(%s)"; + ms.addReplacement(MetaString::RES_NAMES, producedResource); + } + cb->setHoverName(this,&ms); + + InfoWindow iw; + iw.soundID = soundBase::FLAGMINE; + iw.text.addTxt(MetaString::MINE_EVNTS,producedResource); //not use subID, abandoned mines uses default mine texts + iw.player = player; + iw.components.push_back(Component(Component::RESOURCE,producedResource,producedQuantity,-1)); + cb->showInfoDialog(&iw); +} + +ui32 CGMine::defaultResProduction() +{ + switch(producedResource) + { + case Res::WOOD: + case Res::ORE: + return 2; + case Res::GOLD: + return 1000; + default: + return 1; + } +} + +void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner == 0) //attacker won + { + if(subID == 7) + { + showInfoDialog(hero->tempOwner, 85, 0); + } + flagMine(hero->tempOwner); + } +} + +void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if(answer) + cb->startBattleI(hero, this); +} + +void CGResource::initObj() +{ + blockVisit = true; + hoverName = VLC->generaltexth->restypes[subID]; + + if(!amount) + { + switch(subID) + { + case 6: + amount = cb->gameState()->getRandomGenerator().nextInt(500, 1000); + break; + case 0: case 2: + amount = cb->gameState()->getRandomGenerator().nextInt(6, 10); + break; + default: + amount = cb->gameState()->getRandomGenerator().nextInt(3, 5); + break; + } + } +} + +void CGResource::onHeroVisit( const CGHeroInstance * h ) const +{ + if(stacksCount()) + { + if(message.size()) + { + BlockingDialog ynd(true,false); + ynd.player = h->getOwner(); + ynd.text << message; + cb->showBlockingDialog(&ynd); + } + else + { + blockingDialogAnswered(h, true); //behave as if player accepted battle + } + } + else + { + if(message.length()) + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.text << message; + cb->showInfoDialog(&iw); + } + collectRes(h->getOwner()); + } +} + +void CGResource::collectRes( PlayerColor player ) const +{ + cb->giveResource(player, static_cast(subID), amount); + ShowInInfobox sii; + sii.player = player; + sii.c = Component(Component::RESOURCE,subID,amount,0); + sii.text.addTxt(MetaString::ADVOB_TXT,113); + sii.text.addReplacement(MetaString::RES_NAMES, subID); + cb->showCompInfo(&sii); + cb->removeObject(this); +} + +void CGResource::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner == 0) //attacker won + collectRes(hero->getOwner()); +} + +void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if(answer) + cb->startBattleI(hero, this); +} + +void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const +{ + ObjectInstanceID destinationid; + switch(ID) + { + case Obj::MONOLITH_ONE_WAY_ENTRANCE: //one way - find corresponding exit monolith + { + if(vstd::contains(objs,Obj::MONOLITH_ONE_WAY_EXIT) && vstd::contains(objs[Obj::MONOLITH_ONE_WAY_EXIT],subID) && objs[Obj::MONOLITH_ONE_WAY_EXIT][subID].size()) + { + destinationid = *RandomGeneratorUtil::nextItem(objs[Obj::MONOLITH_ONE_WAY_EXIT][subID], cb->gameState()->getRandomGenerator()); + } + else + { + logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; + } + break; + } + case Obj::MONOLITH_TWO_WAY://two way monolith - pick any other one + case Obj::WHIRLPOOL: //Whirlpool + if(vstd::contains(objs,ID) && vstd::contains(objs[ID],subID) && objs[ID][subID].size()>1) + { + //choose another exit + do + { + destinationid = *RandomGeneratorUtil::nextItem(objs[ID][subID], cb->gameState()->getRandomGenerator()); + } while(destinationid == id); + + if (ID == Obj::WHIRLPOOL) + { + if (!h->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION)) + { + if (h->Slots().size() > 1 || h->Slots().begin()->second->count > 1) + { //we can't remove last unit + SlotID targetstack = h->Slots().begin()->first; //slot numbers may vary + for(auto i = h->Slots().rbegin(); i != h->Slots().rend(); i++) + { + if (h->getPower(targetstack) > h->getPower(i->first)) + { + targetstack = (i->first); + } + } + + TQuantity countToTake = h->getStackCount(targetstack) * 0.5; + vstd::amax(countToTake, 1); + + + InfoWindow iw; + iw.player = h->tempOwner; + iw.text.addTxt (MetaString::ADVOB_TXT, 168); + iw.components.push_back (Component(CStackBasicDescriptor(h->getCreature(targetstack), countToTake))); + cb->showInfoDialog(&iw); + cb->changeStackCount(StackLocation(h, targetstack), -countToTake); + } + } + } + } + else + logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; + break; + case Obj::SUBTERRANEAN_GATE: //find nearest subterranean gate on the other level + { + destinationid = getMatchingGate(id); + if(destinationid == ObjectInstanceID()) //no exit + { + showInfoDialog(h,153,0);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged. + } + break; + } + } + if(destinationid == ObjectInstanceID()) + { + logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( "; + return; + } + if (ID == Obj::WHIRLPOOL) + { + std::set tiles = cb->getObj(destinationid)->getBlockedPos(); + auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()); + cb->moveHero(h->id, tile + int3(1,0,0), true); + } + else + cb->moveHero (h->id,CGHeroInstance::convertPosition(cb->getObj(destinationid)->pos,true) - getVisitableOffset(), true); +} + +void CGTeleport::initObj() +{ + int si = subID; + switch (ID) + { + case Obj::SUBTERRANEAN_GATE://ignore subterranean gates subid + case Obj::WHIRLPOOL: + { + si = 0; + break; + } + default: + break; + } + objs[ID][si].push_back(id); +} + +void CGTeleport::postInit() //matches subterranean gates into pairs +{ + //split on underground and surface gates + std::vector gatesSplit[2]; //surface and underground gates + for(auto & elem : objs[Obj::SUBTERRANEAN_GATE][0]) + { + const CGObjectInstance *hlp = cb->getObj(elem); + gatesSplit[hlp->pos.z].push_back(hlp); + } + + //sort by position + std::sort(gatesSplit[0].begin(), gatesSplit[0].end(), [](const CGObjectInstance * a, const CGObjectInstance * b) + { + return a->pos < b->pos; + }); + + for(size_t i = 0; i < gatesSplit[0].size(); i++) + { + const CGObjectInstance *cur = gatesSplit[0][i]; + + //find nearest underground exit + std::pair best(-1, std::numeric_limits::max()); //pair + for(int j = 0; j < gatesSplit[1].size(); j++) + { + const CGObjectInstance *checked = gatesSplit[1][j]; + if(!checked) + continue; + si32 hlp = checked->pos.dist2dSQ(cur->pos); + if(hlp < best.second) + { + best.first = j; + best.second = hlp; + } + } + + if(best.first >= 0) //found pair + { + gates.push_back(std::make_pair(cur->id, gatesSplit[1][best.first]->id)); + gatesSplit[1][best.first] = nullptr; + } + else + gates.push_back(std::make_pair(cur->id, ObjectInstanceID())); + } + objs.erase(Obj::SUBTERRANEAN_GATE); +} + +ObjectInstanceID CGTeleport::getMatchingGate(ObjectInstanceID id) +{ + for(auto & gate : gates) + { + if(gate.first == id) + return gate.second; + if(gate.second == id) + return gate.first; + } + + return ObjectInstanceID(); +} + +void CGArtifact::initObj() +{ + blockVisit = true; + if(ID == Obj::ARTIFACT) + { + hoverName = VLC->arth->artifacts[subID]->Name(); + if(!storedArtifact->artType) + storedArtifact->setType(VLC->arth->artifacts[subID]); + } + if(ID == Obj::SPELL_SCROLL) + subID = 1; + + assert(storedArtifact->artType); + assert(storedArtifact->getParentNodes().size()); + + //assert(storedArtifact->artType->id == subID); //this does not stop desync +} + +void CGArtifact::onHeroVisit( const CGHeroInstance * h ) const +{ + if(!stacksCount()) + { + InfoWindow iw; + iw.player = h->tempOwner; + switch(ID) + { + case Obj::ARTIFACT: + { + iw.soundID = soundBase::treasure; //play sound only for non-scroll arts + iw.components.push_back(Component(Component::ARTIFACT,subID,0,0)); + if(message.length()) + iw.text << message; + else + { + if (VLC->arth->artifacts[subID]->EventText().size()) + iw.text << std::pair (MetaString::ART_EVNTS, subID); + else //fix for mod artifacts with no event text + { + iw.text.addTxt (MetaString::ADVOB_TXT, 183); //% has found treasure + iw.text.addReplacement (h->name); + } + + } + } + break; + case Obj::SPELL_SCROLL: + { + int spellID = storedArtifact->getGivenSpellID(); + iw.components.push_back (Component(Component::SPELL, spellID,0,0)); + iw.text.addTxt (MetaString::ADVOB_TXT,135); + iw.text.addReplacement(MetaString::SPELL_NAME, spellID); + } + break; + } + cb->showInfoDialog(&iw); + pick(h); + } + else + { + if(message.size()) + { + BlockingDialog ynd(true,false); + ynd.player = h->getOwner(); + ynd.text << message; + cb->showBlockingDialog(&ynd); + } + else + { + blockingDialogAnswered(h, true); + } + } +} + +void CGArtifact::pick(const CGHeroInstance * h) const +{ + cb->giveHeroArtifact(h, storedArtifact, ArtifactPosition::FIRST_AVAILABLE); + cb->removeObject(this); +} + +void CGArtifact::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner == 0) //attacker won + pick(hero); +} + +void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if(answer) + cb->startBattleI(hero, this); +} + +void CGWitchHut::initObj() +{ + ability = *RandomGeneratorUtil::nextItem(allowedAbilities, cb->gameState()->getRandomGenerator()); +} + +void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const +{ + InfoWindow iw; + iw.soundID = soundBase::gazebo; + iw.player = h->getOwner(); + if(!wasVisited(h->tempOwner)) + cb->setObjProperty(id, 10, h->tempOwner.getNum()); + ui32 txt_id; + if(h->getSecSkillLevel(SecondarySkill(ability))) //you already know this skill + { + txt_id =172; + } + else if(!h->canLearnSkill()) //already all skills slots used + { + txt_id = 173; + } + else //give sec skill + { + iw.components.push_back(Component(Component::SEC_SKILL, ability, 1, 0)); + txt_id = 171; + cb->changeSecSkill(h, SecondarySkill(ability), 1, true); + } + + iw.text.addTxt(MetaString::ADVOB_TXT,txt_id); + iw.text.addReplacement(MetaString::SEC_SKILL_NAME, ability); + cb->showInfoDialog(&iw); +} + +const std::string & CGWitchHut::getHoverText() const +{ + hoverName = VLC->objtypeh->getObjectName(ID); + if(wasVisited(cb->getLocalPlayer())) + { + hoverName += "\n" + VLC->generaltexth->allTexts[356]; // + (learn %s) + boost::algorithm::replace_first(hoverName,"%s",VLC->generaltexth->skillName[ability]); + const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); + if(h && h->getSecSkillLevel(SecondarySkill(ability))) //hero knows that ability + hoverName += "\n\n" + VLC->generaltexth->allTexts[357]; // (Already learned) + } + return hoverName; +} + +void CGMagicWell::onHeroVisit( const CGHeroInstance * h ) const +{ + int message; + + if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Well today + { + message = 78;//"A second drink at the well in one day will not help you." + } + else if(h->mana < h->manaLimit()) + { + giveDummyBonus(h->id); + cb->setManaPoints(h->id,h->manaLimit()); + message = 77; + } + else + { + message = 79; + } + showInfoDialog(h,message,soundBase::faerie); +} + +const std::string & CGMagicWell::getHoverText() const +{ + getNameVis(hoverName); + return hoverName; +} + +void CGObservatory::onHeroVisit( const CGHeroInstance * h ) const +{ + InfoWindow iw; + iw.player = h->tempOwner; + switch (ID) + { + case Obj::REDWOOD_OBSERVATORY: + case Obj::PILLAR_OF_FIRE: + { + iw.soundID = soundBase::LIGHTHOUSE; + iw.text.addTxt(MetaString::ADVOB_TXT,98 + (ID==Obj::PILLAR_OF_FIRE)); + + FoWChange fw; + fw.player = h->tempOwner; + fw.mode = 1; + cb->getTilesInRange (fw.tiles, pos, 20, h->tempOwner, 1); + cb->sendAndApply (&fw); + break; + } + case Obj::COVER_OF_DARKNESS: + { + iw.text.addTxt (MetaString::ADVOB_TXT, 31); + hideTiles(h->tempOwner, 20); + break; + } + } + cb->showInfoDialog(&iw); +} + +void CGShrine::onHeroVisit( const CGHeroInstance * h ) const +{ + if(spell == SpellID::NONE) + { + logGlobal->errorStream() << "Not initialized shrine visited!"; + return; + } + + if(!wasVisited(h->tempOwner)) + cb->setObjProperty(id, 10, h->tempOwner.getNum()); + + InfoWindow iw; + iw.soundID = soundBase::temple; + iw.player = h->getOwner(); + iw.text.addTxt(MetaString::ADVOB_TXT,127 + ID - 88); + iw.text.addTxt(MetaString::SPELL_NAME,spell); + iw.text << "."; + + if(!h->getArt(ArtifactPosition::SPELLBOOK)) + { + iw.text.addTxt(MetaString::ADVOB_TXT,131); + } + else if(ID == Obj::SHRINE_OF_MAGIC_THOUGHT && !h->getSecSkillLevel(SecondarySkill::WISDOM)) //it's third level spell and hero doesn't have wisdom + { + iw.text.addTxt(MetaString::ADVOB_TXT,130); + } + else if(vstd::contains(h->spells,spell))//hero already knows the spell + { + iw.text.addTxt(MetaString::ADVOB_TXT,174); + } + else //give spell + { + std::set spells; + spells.insert(spell); + cb->changeSpells(h, true, spells); + + iw.components.push_back(Component(Component::SPELL,spell,0,0)); + } + + cb->showInfoDialog(&iw); +} + +void CGShrine::initObj() +{ + if(spell == SpellID::NONE) //spell not set + { + int level = ID-87; + std::vector possibilities; + cb->getAllowedSpells (possibilities, level); + + if(possibilities.empty()) + { + logGlobal->errorStream() << "Error: cannot init shrine, no allowed spells!"; + return; + } + + spell = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator()); + } +} + +const std::string & CGShrine::getHoverText() const +{ + hoverName = VLC->objtypeh->getObjectName(ID); + if(wasVisited(cb->getCurrentPlayer())) //TODO: use local player, not current + { + hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s) + boost::algorithm::replace_first(hoverName,"%s", spell.toSpell()->name); + const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); + if(h && vstd::contains(h->spells,spell)) //hero knows that ability + hoverName += "\n\n" + VLC->generaltexth->allTexts[354]; // (Already learned) + } + return hoverName; +} + +void CGSignBottle::initObj() +{ + //if no text is set than we pick random from the predefined ones + if(message.empty()) + { + message = *RandomGeneratorUtil::nextItem(VLC->generaltexth->randsign, cb->gameState()->getRandomGenerator()); + } + + if(ID == Obj::OCEAN_BOTTLE) + { + blockVisit = true; + } +} + +void CGSignBottle::onHeroVisit( const CGHeroInstance * h ) const +{ + InfoWindow iw; + iw.soundID = soundBase::STORE; + iw.player = h->getOwner(); + iw.text << message; + cb->showInfoDialog(&iw); + + if(ID == Obj::OCEAN_BOTTLE) + cb->removeObject(this); +} + +//TODO: remove +//void CGScholar::giveAnyBonus( const CGHeroInstance * h ) const +//{ +// +//} + +void CGScholar::onHeroVisit( const CGHeroInstance * h ) const +{ + + EBonusType type = bonusType; + int bid = bonusID; + //check if the bonus if applicable, if not - give primary skill (always possible) + int ssl = h->getSecSkillLevel(SecondarySkill(bid)); //current sec skill level, used if bonusType == 1 + if((type == SECONDARY_SKILL + && ((ssl == 3) || (!ssl && !h->canLearnSkill()))) ////hero already has expert level in the skill or (don't know skill and doesn't have free slot) + || (type == SPELL && (!h->getArt(ArtifactPosition::SPELLBOOK) || vstd::contains(h->spells, (ui32) bid) + || ( SpellID(bid).toSpell()->level > h->getSecSkillLevel(SecondarySkill::WISDOM) + 2) + ))) //hero doesn't have a spellbook or already knows the spell or doesn't have Wisdom + { + type = PRIM_SKILL; + bid = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS - 1); + } + + InfoWindow iw; + iw.soundID = soundBase::gazebo; + iw.player = h->getOwner(); + iw.text.addTxt(MetaString::ADVOB_TXT,115); + + switch (type) + { + case PRIM_SKILL: + cb->changePrimSkill(h,static_cast(bid),+1); + iw.components.push_back(Component(Component::PRIM_SKILL,bid,+1,0)); + break; + case SECONDARY_SKILL: + cb->changeSecSkill(h,SecondarySkill(bid),+1); + iw.components.push_back(Component(Component::SEC_SKILL,bid,ssl+1,0)); + break; + case SPELL: + { + std::set hlp; + hlp.insert(SpellID(bid)); + cb->changeSpells(h,true,hlp); + iw.components.push_back(Component(Component::SPELL,bid,0,0)); + } + break; + default: + logGlobal->errorStream() << "Error: wrong bonus type (" << (int)type << ") for Scholar!\n"; + return; + } + + cb->showInfoDialog(&iw); + cb->removeObject(this); +} + +void CGScholar::initObj() +{ + blockVisit = true; + if(bonusType == RANDOM) + { + bonusType = static_cast(cb->gameState()->getRandomGenerator().nextInt(2)); + switch(bonusType) + { + case PRIM_SKILL: + bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS -1); + break; + case SECONDARY_SKILL: + bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::SKILL_QUANTITY -1); + break; + case SPELL: + std::vector possibilities; + for (int i = 1; i < 6; ++i) + cb->getAllowedSpells (possibilities, i); + bonusID = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator()); + break; + } + } +} + +void CGGarrison::onHeroVisit (const CGHeroInstance *h) const +{ + int ally = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); + if (!ally && stacksCount() > 0) { + //TODO: Find a way to apply magic garrison effects in battle. + cb->startBattleI(h, this); + return; + } + + //New owner. + if (!ally) + cb->setOwner(this, h->tempOwner); + + cb->showGarrisonDialog(id, h->id, removableUnits); +} + +ui8 CGGarrison::getPassableness() const +{ + if ( !stacksCount() )//empty - anyone can visit + return GameConstants::ALL_PLAYERS; + if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit + return 0; + + ui8 mask = 0; + TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner); + for(PlayerColor it : ts->players) + mask |= 1<tempOwner; + cv.focusTime = 2000; + + FoWChange fw; + fw.player = h->tempOwner; + fw.mode = 1; + + for(auto it : eyelist[subID]) + { + const CGObjectInstance *eye = cb->getObj(it); + + cb->getTilesInRange (fw.tiles, eye->pos, 10, h->tempOwner, 1); + cb->sendAndApply(&fw); + cv.pos = eye->pos; + + cb->sendAndApply(&cv); + } + cv.pos = h->getPosition(false); + cb->sendAndApply(&cv); + } + } + else if (ID == Obj::EYE_OF_MAGI) + { + showInfoDialog(h,48,soundBase::invalid); + } + +} +void CGBoat::initObj() +{ + hero = nullptr; +} + +void CGSirens::initObj() +{ + blockVisit = true; +} + +const std::string & CGSirens::getHoverText() const +{ + getNameVis(hoverName); + return hoverName; +} + +void CGSirens::onHeroVisit( const CGHeroInstance * h ) const +{ + InfoWindow iw; + iw.soundID = soundBase::DANGER; + iw.player = h->tempOwner; + if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Sirens + { + iw.text.addTxt(MetaString::ADVOB_TXT,133); + } + else + { + giveDummyBonus(h->id, Bonus::ONE_BATTLE); + TExpType xp = 0; + + for (auto i = h->Slots().begin(); i != h->Slots().end(); i++) + { + TQuantity drown = i->second->count * 0.3; + if(drown) + { + cb->changeStackCount(StackLocation(h, i->first), -drown); + xp += drown * i->second->type->valOfBonuses(Bonus::STACK_HEALTH); + } + } + + if(xp) + { + xp = h->calculateXp(xp); + iw.text.addTxt(MetaString::ADVOB_TXT,132); + iw.text.addReplacement(xp); + cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, xp, false); + } + else + { + iw.text.addTxt(MetaString::ADVOB_TXT,134); + } + } + cb->showInfoDialog(&iw); + +} + +CGShipyard::CGShipyard() + :IShipyard(this) +{ +} + +void CGShipyard::getOutOffsets( std::vector &offsets ) const +{ + // H J L K I + // A x S x B + // C E G F D + offsets += int3(-3,0,0), int3(1,0,0), //AB + int3(-3,1,0), int3(1,1,0), int3(-2,1,0), int3(0,1,0), int3(-1,1,0), //CDEFG + int3(-3,-1,0), int3(1,-1,0), int3(-2,-1,0), int3(0,-1,0), int3(-1,-1,0); //HIJKL +} + +void CGShipyard::onHeroVisit( const CGHeroInstance * h ) const +{ + if(!cb->gameState()->getPlayerRelations(tempOwner, h->tempOwner)) + cb->setOwner(this, h->tempOwner); + + auto s = shipyardStatus(); + if(s != IBoatGenerator::GOOD) + { + InfoWindow iw; + iw.player = tempOwner; + getProblemText(iw.text, h); + cb->showInfoDialog(&iw); + } + else + { + openWindow(OpenWindow::SHIPYARD_WINDOW,id.getNum(),h->id.getNum()); + } +} + +void CCartographer::onHeroVisit( const CGHeroInstance * h ) const +{ + if (!wasVisited (h->getOwner()) ) //if hero has not visited yet this cartographer + { + if (cb->getResource(h->tempOwner, Res::GOLD) >= 1000) //if he can afford a map + { + //ask if he wants to buy one + int text=0; + switch (subID) + { + case 0: + text = 25; + break; + case 1: + text = 26; + break; + case 2: + text = 27; + break; + default: + logGlobal->warnStream() << "Unrecognized subtype of cartographer"; + } + assert(text); + BlockingDialog bd (true, false); + bd.player = h->getOwner(); + bd.soundID = soundBase::LIGHTHOUSE; + bd.text.addTxt (MetaString::ADVOB_TXT, text); + cb->showBlockingDialog (&bd); + } + else //if he cannot afford + { + showInfoDialog(h,28,soundBase::CAVEHEAD); + } + } + else //if he already visited carographer + { + showInfoDialog(h,24,soundBase::CAVEHEAD); + } +} + +void CCartographer::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if (answer) //if hero wants to buy map + { + cb->giveResource (hero->tempOwner, Res::GOLD, -1000); + FoWChange fw; + fw.mode = 1; + fw.player = hero->tempOwner; + + //subIDs of different types of cartographers: + //water = 0; land = 1; underground = 2; + cb->getAllTiles (fw.tiles, hero->tempOwner, subID - 1, !subID + 1); //reveal appropriate tiles + cb->sendAndApply (&fw); + cb->setObjProperty (id, 10, hero->tempOwner.getNum()); + } +} + +void CGDenOfthieves::onHeroVisit (const CGHeroInstance * h) const +{ + cb->showThievesGuildWindow(h->tempOwner, id); +} + +void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const +{ + InfoWindow iw; + iw.player = h->tempOwner; + TeamState *ts = cb->gameState()->getPlayerTeam(h->tempOwner); + assert(ts); + TeamID team = ts->id; + + if(!wasVisited(team)) + { + iw.text.addTxt(MetaString::ADVOB_TXT, 96); + cb->sendAndApply(&iw); + + cb->setObjProperty(id, 20, h->tempOwner.getNum()); //increment general visited obelisks counter + + openWindow(OpenWindow::PUZZLE_MAP, h->tempOwner.getNum()); + + cb->setObjProperty(id, 10, h->tempOwner.getNum()); //mark that particular obelisk as visited + } + else + { + iw.text.addTxt(MetaString::ADVOB_TXT, 97); + cb->sendAndApply(&iw); + } + +} + +void CGObelisk::initObj() +{ + obeliskCount++; +} + +const std::string & CGObelisk::getHoverText() const +{ + bool visited = wasVisited(cb->getLocalPlayer()); + hoverName = VLC->objtypeh->getObjectName(ID) + " " + visitedTxt(visited); + return hoverName; +} + +void CGObelisk::setPropertyDer( ui8 what, ui32 val ) +{ + CPlayersVisited::setPropertyDer(what, val); + switch(what) + { + case 20: + assert(val < PlayerColor::PLAYER_LIMIT_I); + visited[TeamID(val)]++; + + if(visited[TeamID(val)] > obeliskCount) + { + logGlobal->errorStream() << "Error: Visited " << visited[TeamID(val)] << "\t\t" << obeliskCount; + assert(0); + } + + break; + } +} + +void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const +{ + if(h->tempOwner != tempOwner) + { + PlayerColor oldOwner = tempOwner; + cb->setOwner(this,h->tempOwner); //not ours? flag it! + showInfoDialog(h,69,soundBase::LIGHTHOUSE); + giveBonusTo(h->tempOwner); + + if(oldOwner < PlayerColor::PLAYER_LIMIT) //remove bonus from old owner + { + RemoveBonus rb(RemoveBonus::PLAYER); + rb.whoID = oldOwner.getNum(); + rb.source = Bonus::OBJECT; + rb.id = id.getNum(); + cb->sendAndApply(&rb); + } + } +} + +void CGLighthouse::initObj() +{ + if(tempOwner < PlayerColor::PLAYER_LIMIT) + { + giveBonusTo(tempOwner); + } +} + +const std::string & CGLighthouse::getHoverText() const +{ + hoverName = VLC->objtypeh->getObjectName(ID); + //TODO: owned by %s player + return hoverName; +} + +void CGLighthouse::giveBonusTo( PlayerColor player ) const +{ + GiveBonus gb(GiveBonus::PLAYER); + gb.bonus.type = Bonus::SEA_MOVEMENT; + gb.bonus.val = 500; + gb.id = player.getNum(); + gb.bonus.duration = Bonus::PERMANENT; + gb.bonus.source = Bonus::OBJECT; + gb.bonus.sid = id.getNum(); + cb->sendAndApply(&gb); +} diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h new file mode 100644 index 000000000..c53db75d7 --- /dev/null +++ b/lib/mapObjects/MiscObjects.h @@ -0,0 +1,377 @@ +#pragma once + +#include "CObjectHandler.h" +#include "CArmedInstance.h" + +/* + * MiscObjects.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 + * + */ + +class DLL_LINKAGE CPlayersVisited: public CGObjectInstance +{ +public: + std::set players; //players that visited this object + + bool wasVisited(PlayerColor player) const; + bool wasVisited(TeamID team) const; + void setPropertyDer(ui8 what, ui32 val) override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & players; + } +}; + +class DLL_LINKAGE CGCreature : public CArmedInstance //creatures on map +{ + enum Action { + FIGHT = -2, FLEE = -1, JOIN_FOR_FREE = 0 //values > 0 mean gold price + }; + +public: + ui32 identifier; //unique code for this monster (used in missions) + si8 character; //character of this set of creatures (0 - the most friendly, 4 - the most hostile) => on init changed to -4 (compliant) ... 10 value (savage) + std::string message; //message printed for attacking hero + TResources resources; // resources given to hero that has won with monsters + ArtifactID gainedArtifact; //ID of artifact gained to hero, -1 if none + bool neverFlees; //if true, the troops will never flee + bool notGrowingTeam; //if true, number of units won't grow + ui64 temppower; //used to handle fractional stack growth for tiny stacks + + bool refusedJoining; + + void onHeroVisit(const CGHeroInstance * h) const override; + const std::string & getHoverText() const override; + void initObj() override; + void newTurn() const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + + struct DLL_LINKAGE formationInfo // info about merging stacks after battle back into one + { + si32 basicType; + ui32 randomFormation; //random seed used to determine number of stacks and is there's upgraded stack + template void serialize(Handler &h, const int version) + { + h & basicType & randomFormation; + } + } formation; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & identifier & character & message & resources & gainedArtifact & neverFlees & notGrowingTeam & temppower; + h & refusedJoining & formation; + } +protected: + void setPropertyDer(ui8 what, ui32 val) override; +private: + + void fight(const CGHeroInstance *h) const; + void flee( const CGHeroInstance * h ) const; + void fleeDecision(const CGHeroInstance *h, ui32 pursue) const; + void joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const; + + int takenAction(const CGHeroInstance *h, bool allowJoin=true) const; //action on confrontation: -2 - fight, -1 - flee, >=0 - will join for given value of gold (may be 0) + +}; + + +class DLL_LINKAGE CGSignBottle : public CGObjectInstance //signs and ocean bottles +{ +public: + std::string message; + + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & message; + } +}; + +class DLL_LINKAGE CGWitchHut : public CPlayersVisited +{ +public: + std::vector allowedAbilities; + ui32 ability; + + const std::string & getHoverText() const override; + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & allowedAbilities & ability; + } +}; + +class DLL_LINKAGE CGScholar : public CGObjectInstance +{ +public: + enum EBonusType {PRIM_SKILL, SECONDARY_SKILL, SPELL, RANDOM = 255}; + EBonusType bonusType; + ui16 bonusID; //ID of skill/spell + +// void giveAnyBonus(const CGHeroInstance * h) const; //TODO: remove + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & bonusType & bonusID; + } +}; + +class DLL_LINKAGE CGGarrison : public CArmedInstance +{ +public: + bool removableUnits; + + ui8 getPassableness() const; + void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & removableUnits; + } +}; + +class DLL_LINKAGE CGArtifact : public CArmedInstance +{ +public: + CArtifactInstance *storedArtifact; + std::string message; + + void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + void pick( const CGHeroInstance * h ) const; + void initObj() override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & message & storedArtifact; + } +}; + +class DLL_LINKAGE CGResource : public CArmedInstance +{ +public: + ui32 amount; //0 if random + std::string message; + + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + void collectRes(PlayerColor player) const; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & amount & message; + } +}; + +class DLL_LINKAGE CGShrine : public CPlayersVisited +{ +public: + SpellID spell; //id of spell or NONE if random + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + const std::string & getHoverText() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this);; + h & spell; + } +}; + +class DLL_LINKAGE CGMine : public CArmedInstance +{ +public: + Res::ERes producedResource; + ui32 producedQuantity; + + void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + void flagMine(PlayerColor player) const; + void newTurn() const override; + void initObj() override; + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & producedResource & producedQuantity; + } + ui32 defaultResProduction(); +}; + +class DLL_LINKAGE CGTeleport : public CGObjectInstance //teleports and subterranean gates +{ +public: + static std::map > > objs; //teleports: map[ID][subID] => vector of ids + static std::vector > gates; //subterranean gates: pairs of ids + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + static void postInit(); + static ObjectInstanceID getMatchingGate(ObjectInstanceID id); //receives id of one subterranean gate and returns id of the paired one, -1 if none + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGMagicWell : public CGObjectInstance //objects giving bonuses to luck/morale/movement +{ +public: + void onHeroVisit(const CGHeroInstance * h) const override; + const std::string & getHoverText() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGSirens : public CGObjectInstance +{ +public: + void onHeroVisit(const CGHeroInstance * h) const override; + const std::string & getHoverText() const override; + void initObj() override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGObservatory : public CGObjectInstance //Redwood observatory +{ +public: + void onHeroVisit(const CGHeroInstance * h) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGBoat : public CGObjectInstance +{ +public: + ui8 direction; + const CGHeroInstance *hero; //hero on board + + void initObj() override; + + CGBoat() + { + hero = nullptr; + direction = 4; + } + template void serialize(Handler &h, const int version) + { + h & static_cast(*this) & direction & hero; + } +}; + +class CGShipyard : public CGObjectInstance, public IShipyard +{ +public: + void getOutOffsets(std::vector &offsets) const; //offsets to obj pos when we boat can be placed + CGShipyard(); + void onHeroVisit(const CGHeroInstance * h) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGMagi : public CGObjectInstance +{ +public: + static std::map > eyelist; //[subID][id], supports multiple sets as in H5 + + void initObj() override; + void onHeroVisit(const CGHeroInstance * h) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + + + +class DLL_LINKAGE CCartographer : public CPlayersVisited +{ +///behaviour varies depending on surface and floor +public: + void onHeroVisit(const CGHeroInstance * h) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGDenOfthieves : public CGObjectInstance +{ + void onHeroVisit(const CGHeroInstance * h) const override; +}; + +class DLL_LINKAGE CGObelisk : public CPlayersVisited +{ +public: + static ui8 obeliskCount; //how many obelisks are on map + static std::map visited; //map: team_id => how many obelisks has been visited + + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + const std::string & getHoverText() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +protected: + void setPropertyDer(ui8 what, ui32 val) override; +}; + +class DLL_LINKAGE CGLighthouse : public CGObjectInstance +{ +public: + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + const std::string & getHoverText() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } + void giveBonusTo( PlayerColor player ) const; +}; diff --git a/lib/CDefObjInfoHandler.cpp b/lib/mapObjects/ObjectTemplate.cpp similarity index 65% rename from lib/CDefObjInfoHandler.cpp rename to lib/mapObjects/ObjectTemplate.cpp index 3ee27eab8..6cec38ba1 100644 --- a/lib/CDefObjInfoHandler.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -1,427 +1,334 @@ -#include "StdInc.h" -#include "CDefObjInfoHandler.h" - -#include "filesystem/Filesystem.h" -#include "filesystem/CBinaryReader.h" -//#include "../client/CGameInfo.h" -#include "../lib/VCMI_Lib.h" -#include "GameConstants.h" -#include "StringConstants.h" -#include "CGeneralTextHandler.h" -#include "CModHandler.h" -#include "JsonNode.h" - -/* - * CDefObjInfoHandler.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -static bool isVisitableFromTop(int identifier, int type) -{ - if(type == 2 || type == 3 || type == 4 || type == 5) //creature, hero, artifact, resource - return true; - - static const Obj visitableFromTop[] = - {Obj::FLOTSAM, - Obj::SEA_CHEST, - Obj::SHIPWRECK_SURVIVOR, - Obj::BUOY, - Obj::OCEAN_BOTTLE, - Obj::BOAT, - Obj::WHIRLPOOL, - Obj::GARRISON, - Obj::GARRISON2, - Obj::SCHOLAR, - Obj::CAMPFIRE, - Obj::BORDERGUARD, - Obj::BORDER_GATE, - Obj::QUEST_GUARD, - Obj::CORPSE - }; - if (vstd::find_pos(visitableFromTop, identifier) != -1) - return true; - return false; -} - -ObjectTemplate::ObjectTemplate(): - visitDir(8|16|32|64|128), // all but top - id(Obj::NO_OBJ), - subid(0), - printPriority(0) -{ -} - -void ObjectTemplate::readTxt(CLegacyConfigParser & parser) -{ - std::string data = parser.readString(); - std::vector strings; - boost::split(strings, data, boost::is_any_of(" ")); - assert(strings.size() == 9); - - animationFile = strings[0]; - stringID = strings[0]; - - std::string & blockStr = strings[1]; //block map, 0 = blocked, 1 = unblocked - std::string & visitStr = strings[2]; //visit map, 1 = visitable, 0 = not visitable - - assert(blockStr.size() == 6*8); - assert(visitStr.size() == 6*8); - - setSize(8, 6); - for (size_t i=0; i<6; i++) // 6 rows - { - for (size_t j=0; j<8; j++) // 8 columns - { - auto & tile = usedTiles[i][j]; - tile |= VISIBLE; // assume that all tiles are visible - if (blockStr[i*8 + j] == '0') - tile |= BLOCKED; - - if (visitStr[i*8 + j] == '1') - tile |= VISITABLE; - } - } - - // strings[3] most likely - terrains on which this object can be placed in editor. - // e.g. Whirpool can be placed manually only on water while mines can be placed everywhere despite terrain-specific gfx - // so these two fields can be interpreted as "strong affinity" and "weak affinity" towards terrains - std::string & terrStr = strings[4]; // allowed terrains, 1 = object can be placed on this terrain - - assert(terrStr.size() == 9); // all terrains but rock - for (size_t i=0; i<9; i++) - { - if (terrStr[8-i] == '1') - allowedTerrains.insert(ETerrainType(i)); - } - - id = Obj(boost::lexical_cast(strings[5])); - subid = boost::lexical_cast(strings[6]); - int type = boost::lexical_cast(strings[7]); - printPriority = boost::lexical_cast(strings[8]) * 100; // to have some space in future - - if (isVisitableFromTop(id, type)) - visitDir = 0xff; - else - visitDir = (8|16|32|64|128); - - readMsk(); -} - -void ObjectTemplate::readMsk() -{ - ResourceID resID("SPRITES/" + animationFile, EResType::MASK); - - if (CResourceHandler::get()->existsResource(resID)) - { - auto msk = CResourceHandler::get()->load(resID)->readAll(); - setSize(msk.first.get()[0], msk.first.get()[1]); - } - else //maximum possible size of H3 object //TODO: remove hardcode and move this data into modding system - { - setSize(8, 6); - } -} - -void ObjectTemplate::readMap(CBinaryReader & reader) -{ - animationFile = reader.readString(); - - setSize(8, 6); - ui8 blockMask[6]; - ui8 visitMask[6]; - for(auto & byte : blockMask) - byte = reader.readUInt8(); - for(auto & byte : visitMask) - byte = reader.readUInt8(); - - for (size_t i=0; i<6; i++) // 6 rows - { - for (size_t j=0; j<8; j++) // 8 columns - { - auto & tile = usedTiles[5 - i][7 - j]; - tile |= VISIBLE; // assume that all tiles are visible - if (((blockMask[i] >> j) & 1 ) == 0) - tile |= BLOCKED; - - if (((visitMask[i] >> j) & 1 ) != 0) - tile |= VISITABLE; - } - } - - reader.readUInt16(); - ui16 terrMask = reader.readUInt16(); - for (size_t i=0; i<9; i++) - { - if (((terrMask >> i) & 1 ) != 0) - allowedTerrains.insert(ETerrainType(i)); - } - - id = Obj(reader.readUInt32()); - subid = reader.readUInt32(); - int type = reader.readUInt8(); - printPriority = reader.readUInt8() * 100; // to have some space in future - - if (isVisitableFromTop(id, type)) - visitDir = 0xff; - else - visitDir = (8|16|32|64|128); - - reader.skip(16); - readMsk(); - - if (id == Obj::EVENT) - { - setSize(1,1); - usedTiles[0][0] = VISITABLE; - } -} - -void ObjectTemplate::readJson(const JsonNode &node) -{ - id = Obj(node["basebase"].Float()); // temporary, should be removed and determined indirectly via object type parent (e.g. base->base) - subid = node["base"].Float(); - animationFile = node["animation"].String(); - - const JsonVector & visitDirs = node["visitableFrom"].Vector(); - if (!visitDirs.empty()) - { - if (visitDirs[0].String()[0] == '+') visitDir |= 1; - if (visitDirs[0].String()[1] == '+') visitDir |= 2; - if (visitDirs[0].String()[2] == '+') visitDir |= 4; - if (visitDirs[1].String()[2] == '+') visitDir |= 8; - if (visitDirs[2].String()[2] == '+') visitDir |= 16; - if (visitDirs[2].String()[1] == '+') visitDir |= 32; - if (visitDirs[2].String()[0] == '+') visitDir |= 64; - if (visitDirs[1].String()[0] == '+') visitDir |= 128; - } - else - visitDir = 0x00; - - if (!node["allowedTerrains"].isNull()) - { - for (auto & entry : node["allowedTerrains"].Vector()) - allowedTerrains.insert(ETerrainType(vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.String()))); - } - else - { - for (size_t i=0; i< GameConstants::TERRAIN_TYPES; i++) - allowedTerrains.insert(ETerrainType(i)); - } - - auto charToTile = [&](const char & ch) -> ui8 - { - switch (ch) - { - case ' ' : return 0; - case '0' : return 0; - case 'V' : return VISIBLE; - case 'B' : return VISIBLE | BLOCKED; - case 'H' : return BLOCKED; - case 'A' : return VISIBLE | BLOCKED | VISITABLE; - case 'T' : return BLOCKED | VISITABLE; - default: - logGlobal->errorStream() << "Unrecognized char " << ch << " in template mask"; - return 0; - } - }; - - const JsonVector & mask = node["mask"].Vector(); - - size_t height = mask.size(); - size_t width = 0; - for (auto & line : mask) - vstd::amax(width, line.String().size()); - - setSize(width, height); - - for (size_t i=0; i= getWidth() || Y >= getHeight()) - return false; - return true; -} - -bool ObjectTemplate::isVisitableAt(si32 X, si32 Y) const -{ - if (isWithin(X, Y)) - return usedTiles[Y][X] & VISITABLE; - return false; -} - -bool ObjectTemplate::isVisibleAt(si32 X, si32 Y) const -{ - if (isWithin(X, Y)) - return usedTiles[Y][X] & VISIBLE; - return false; -} - -bool ObjectTemplate::isBlockedAt(si32 X, si32 Y) const -{ - if (isWithin(X, Y)) - return usedTiles[Y][X] & BLOCKED; - return false; -} - -bool ObjectTemplate::isVisitableFrom(si8 X, si8 Y) const -{ - // visitDir uses format - // 1 2 3 - // 8 4 - // 7 6 5 - int dirMap[3][3] = - { - { visitDir & 1, visitDir & 2, visitDir & 4 }, - { visitDir & 128, 1 , visitDir & 8 }, - { visitDir & 64, visitDir & 32, visitDir & 16 } - }; - // map input values to range 0..2 - int dx = X < 0 ? 0 : X == 0 ? 1 : 2; - int dy = Y < 0 ? 0 : Y == 0 ? 1 : 2; - - return dirMap[dy][dx] != 0; -} - -bool ObjectTemplate::canBePlacedAt(ETerrainType terrain) const -{ - return allowedTerrains.count(terrain) != 0; -} - -void CDefObjInfoHandler::readTextFile(std::string path) -{ - CLegacyConfigParser parser(path); - size_t totalNumber = parser.readNumber(); // first line contains number of objects to read and nothing else - parser.endLine(); - - for (size_t i=0; i newTemplates; - newTemplates.reserve(node.Struct().size()); - - // load all new templates - for (auto & entry : node.Struct()) - { - JsonUtils::validate(entry.second, "vcmi:objectTemplate", entry.first); - - ObjectTemplate templ; - templ.stringID = entry.first; - templ.readJson(entry.second); - newTemplates.push_back(templ); - } - - // erase old ones to avoid conflicts - for (auto & entry : newTemplates) - eraseAll(entry.id, entry.subid); - - // merge new templates into storage - objects.insert(objects.end(), newTemplates.begin(), newTemplates.end()); -} - -void CDefObjInfoHandler::eraseAll(Obj type, si32 subtype) -{ - auto it = std::remove_if(objects.begin(), objects.end(), [&](const ObjectTemplate & obj) - { - return obj.id == type && obj.subid == subtype; - }); - objects.erase(it, objects.end()); -} - -void CDefObjInfoHandler::registerTemplate(ObjectTemplate obj) -{ - objects.push_back(obj); -} - -std::vector CDefObjInfoHandler::pickCandidates(Obj type, si32 subtype) const -{ - std::vector ret; - - std::copy_if(objects.begin(), objects.end(), std::back_inserter(ret), [&](const ObjectTemplate & obj) - { - return obj.id == type && obj.subid == subtype; - }); - if (ret.empty()) - logGlobal->errorStream() << "Failed to find template for " << type << ":" << subtype; - - assert(!ret.empty()); // Can't create object of this type/subtype - return ret; -} - -std::vector CDefObjInfoHandler::pickCandidates(Obj type, si32 subtype, ETerrainType terrain) const -{ - std::vector ret = pickCandidates(type, subtype); - std::vector filtered; - - std::copy_if(ret.begin(), ret.end(), std::back_inserter(filtered), [&](const ObjectTemplate & obj) - { - return obj.canBePlacedAt(terrain); - }); - // it is possible that there are no templates usable on specific terrain. In this case - return list before filtering - return filtered.empty() ? ret : filtered; -} - -std::vector CDefObjInfoHandler::pickCandidates(Obj type, si32 subtype, ETerrainType terrain, std::function filter) const -{ - std::vector ret = pickCandidates(type, subtype, terrain); - std::vector filtered; - - std::copy_if(ret.begin(), ret.end(), std::back_inserter(filtered), filter); - // it is possible that there are no templates usable on specific terrain. In this case - return list before filtering - return filtered.empty() ? ret : filtered; -} +#include "StdInc.h" +#include "CObjectClassesHandler.h" + +#include "../filesystem/Filesystem.h" +#include "../filesystem/CBinaryReader.h" +#include "../lib/VCMI_Lib.h" +#include "../GameConstants.h" +#include "../StringConstants.h" +#include "../CGeneralTextHandler.h" +#include "CObjectHandler.h" +#include "../CModHandler.h" +#include "../JsonNode.h" + +#include "CRewardableConstructor.h" + +/* + * ObjectTemplate.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +static bool isVisitableFromTop(int identifier, int type) +{ + if(type == 2 || type == 3 || type == 4 || type == 5) //creature, hero, artifact, resource + return true; + + static const Obj visitableFromTop[] = + {Obj::FLOTSAM, + Obj::SEA_CHEST, + Obj::SHIPWRECK_SURVIVOR, + Obj::BUOY, + Obj::OCEAN_BOTTLE, + Obj::BOAT, + Obj::WHIRLPOOL, + Obj::GARRISON, + Obj::GARRISON2, + Obj::SCHOLAR, + Obj::CAMPFIRE, + Obj::BORDERGUARD, + Obj::BORDER_GATE, + Obj::QUEST_GUARD, + Obj::CORPSE + }; + if (vstd::find_pos(visitableFromTop, identifier) != -1) + return true; + return false; +} + +ObjectTemplate::ObjectTemplate(): + visitDir(8|16|32|64|128), // all but top + id(Obj::NO_OBJ), + subid(0), + printPriority(0) +{ +} + +void ObjectTemplate::readTxt(CLegacyConfigParser & parser) +{ + std::string data = parser.readString(); + std::vector strings; + boost::split(strings, data, boost::is_any_of(" ")); + assert(strings.size() == 9); + + animationFile = strings[0]; + stringID = strings[0]; + + std::string & blockStr = strings[1]; //block map, 0 = blocked, 1 = unblocked + std::string & visitStr = strings[2]; //visit map, 1 = visitable, 0 = not visitable + + assert(blockStr.size() == 6*8); + assert(visitStr.size() == 6*8); + + setSize(8, 6); + for (size_t i=0; i<6; i++) // 6 rows + { + for (size_t j=0; j<8; j++) // 8 columns + { + auto & tile = usedTiles[i][j]; + tile |= VISIBLE; // assume that all tiles are visible + if (blockStr[i*8 + j] == '0') + tile |= BLOCKED; + + if (visitStr[i*8 + j] == '1') + tile |= VISITABLE; + } + } + + // strings[3] most likely - terrains on which this object can be placed in editor. + // e.g. Whirpool can be placed manually only on water while mines can be placed everywhere despite terrain-specific gfx + // so these two fields can be interpreted as "strong affinity" and "weak affinity" towards terrains + std::string & terrStr = strings[4]; // allowed terrains, 1 = object can be placed on this terrain + + assert(terrStr.size() == 9); // all terrains but rock + for (size_t i=0; i<9; i++) + { + if (terrStr[8-i] == '1') + allowedTerrains.insert(ETerrainType(i)); + } + + id = Obj(boost::lexical_cast(strings[5])); + subid = boost::lexical_cast(strings[6]); + int type = boost::lexical_cast(strings[7]); + printPriority = boost::lexical_cast(strings[8]) * 100; // to have some space in future + + if (isVisitableFromTop(id, type)) + visitDir = 0xff; + else + visitDir = (8|16|32|64|128); + + readMsk(); +} + +void ObjectTemplate::readMsk() +{ + ResourceID resID("SPRITES/" + animationFile, EResType::MASK); + + if (CResourceHandler::get()->existsResource(resID)) + { + auto msk = CResourceHandler::get()->load(resID)->readAll(); + setSize(msk.first.get()[0], msk.first.get()[1]); + } + else //maximum possible size of H3 object //TODO: remove hardcode and move this data into modding system + { + setSize(8, 6); + } +} + +void ObjectTemplate::readMap(CBinaryReader & reader) +{ + animationFile = reader.readString(); + + setSize(8, 6); + ui8 blockMask[6]; + ui8 visitMask[6]; + for(auto & byte : blockMask) + byte = reader.readUInt8(); + for(auto & byte : visitMask) + byte = reader.readUInt8(); + + for (size_t i=0; i<6; i++) // 6 rows + { + for (size_t j=0; j<8; j++) // 8 columns + { + auto & tile = usedTiles[5 - i][7 - j]; + tile |= VISIBLE; // assume that all tiles are visible + if (((blockMask[i] >> j) & 1 ) == 0) + tile |= BLOCKED; + + if (((visitMask[i] >> j) & 1 ) != 0) + tile |= VISITABLE; + } + } + + reader.readUInt16(); + ui16 terrMask = reader.readUInt16(); + for (size_t i=0; i<9; i++) + { + if (((terrMask >> i) & 1 ) != 0) + allowedTerrains.insert(ETerrainType(i)); + } + + id = Obj(reader.readUInt32()); + subid = reader.readUInt32(); + int type = reader.readUInt8(); + printPriority = reader.readUInt8() * 100; // to have some space in future + + if (isVisitableFromTop(id, type)) + visitDir = 0xff; + else + visitDir = (8|16|32|64|128); + + reader.skip(16); + readMsk(); + + if (id == Obj::EVENT) + { + setSize(1,1); + usedTiles[0][0] = VISITABLE; + } +} + +void ObjectTemplate::readJson(const JsonNode &node) +{ + animationFile = node["animation"].String(); + + const JsonVector & visitDirs = node["visitableFrom"].Vector(); + if (!visitDirs.empty()) + { + if (visitDirs[0].String()[0] == '+') visitDir |= 1; + if (visitDirs[0].String()[1] == '+') visitDir |= 2; + if (visitDirs[0].String()[2] == '+') visitDir |= 4; + if (visitDirs[1].String()[2] == '+') visitDir |= 8; + if (visitDirs[2].String()[2] == '+') visitDir |= 16; + if (visitDirs[2].String()[1] == '+') visitDir |= 32; + if (visitDirs[2].String()[0] == '+') visitDir |= 64; + if (visitDirs[1].String()[0] == '+') visitDir |= 128; + } + else + visitDir = 0x00; + + if (!node["allowedTerrains"].isNull()) + { + for (auto & entry : node["allowedTerrains"].Vector()) + allowedTerrains.insert(ETerrainType(vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.String()))); + } + else + { + for (size_t i=0; i< GameConstants::TERRAIN_TYPES; i++) + allowedTerrains.insert(ETerrainType(i)); + } + + if (allowedTerrains.empty()) + logGlobal->warnStream() << "Loaded template without allowed terrains!"; + + auto charToTile = [&](const char & ch) -> ui8 + { + switch (ch) + { + case ' ' : return 0; + case '0' : return 0; + case 'V' : return VISIBLE; + case 'B' : return VISIBLE | BLOCKED; + case 'H' : return BLOCKED; + case 'A' : return VISIBLE | BLOCKED | VISITABLE; + case 'T' : return BLOCKED | VISITABLE; + default: + logGlobal->errorStream() << "Unrecognized char " << ch << " in template mask"; + return 0; + } + }; + + const JsonVector & mask = node["mask"].Vector(); + + size_t height = mask.size(); + size_t width = 0; + for (auto & line : mask) + vstd::amax(width, line.String().size()); + + setSize(width, height); + + for (size_t i=0; i= getWidth() || Y >= getHeight()) + return false; + return true; +} + +bool ObjectTemplate::isVisitableAt(si32 X, si32 Y) const +{ + if (isWithin(X, Y)) + return usedTiles[Y][X] & VISITABLE; + return false; +} + +bool ObjectTemplate::isVisibleAt(si32 X, si32 Y) const +{ + if (isWithin(X, Y)) + return usedTiles[Y][X] & VISIBLE; + return false; +} + +bool ObjectTemplate::isBlockedAt(si32 X, si32 Y) const +{ + if (isWithin(X, Y)) + return usedTiles[Y][X] & BLOCKED; + return false; +} + +bool ObjectTemplate::isVisitableFrom(si8 X, si8 Y) const +{ + // visitDir uses format + // 1 2 3 + // 8 4 + // 7 6 5 + int dirMap[3][3] = + { + { visitDir & 1, visitDir & 2, visitDir & 4 }, + { visitDir & 128, 1 , visitDir & 8 }, + { visitDir & 64, visitDir & 32, visitDir & 16 } + }; + // map input values to range 0..2 + int dx = X < 0 ? 0 : X == 0 ? 1 : 2; + int dy = Y < 0 ? 0 : Y == 0 ? 1 : 2; + + return dirMap[dy][dx] != 0; +} + +bool ObjectTemplate::canBePlacedAt(ETerrainType terrain) const +{ + return allowedTerrains.count(terrain) != 0; +} + diff --git a/lib/CDefObjInfoHandler.h b/lib/mapObjects/ObjectTemplate.h similarity index 59% rename from lib/CDefObjInfoHandler.h rename to lib/mapObjects/ObjectTemplate.h index ccc41bb7f..89353b26c 100644 --- a/lib/CDefObjInfoHandler.h +++ b/lib/mapObjects/ObjectTemplate.h @@ -1,110 +1,79 @@ -#pragma once - -#include "GameConstants.h" -#include "../lib/ConstTransitivePtr.h" - -/* - * CDefObjInfoHandler.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 - * - */ - -class CBinaryReader; -class CLegacyConfigParser; -class JsonNode; - -class DLL_LINKAGE ObjectTemplate -{ - enum EBlockMapBits - { - VISIBLE = 1, - VISITABLE = 2, - BLOCKED = 4 - }; - - /// tiles that are covered by this object, uses EBlockMapBits enum as flags - std::vector> usedTiles; - /// directions from which object can be entered, format same as for moveDir in CGHeroInstance(but 0 - 7) - ui8 visitDir; - /// list of terrains on which this object can be placed - std::set allowedTerrains; - -public: - /// H3 ID/subID of this object - Obj id; - si32 subid; - /// print priority, objects with higher priority will be print first, below everything else - si32 printPriority; - /// animation file that should be used to display object - std::string animationFile; - - /// string ID, equals to def base name for h3m files (lower case, no extension) or specified in mod data - std::string stringID; - - ui32 getWidth() const; - ui32 getHeight() const; - void setSize(ui32 width, ui32 height); - - bool isVisitable() const; - - // Checks object used tiles - // Position is relative to bottom-right corner of the object, can not be negative - bool isWithin(si32 X, si32 Y) const; - bool isVisitableAt(si32 X, si32 Y) const; - bool isVisibleAt(si32 X, si32 Y) const; - bool isBlockedAt(si32 X, si32 Y) const; - - // Checks if object is visitable from certain direction. X and Y must be between -1..+1 - bool isVisitableFrom(si8 X, si8 Y) const; - - // Checks if object can be placed on specific terrain - bool canBePlacedAt(ETerrainType terrain) const; - - ObjectTemplate(); - - void readTxt(CLegacyConfigParser & parser); - void readMsk(); - void readMap(CBinaryReader & reader); - void readJson(const JsonNode & node); - - template void serialize(Handler &h, const int version) - { - h & usedTiles & allowedTerrains & animationFile & stringID; - h & id & subid & printPriority & visitDir; - } -}; - -class DLL_LINKAGE CDefObjInfoHandler -{ - /// list of all object templates loaded from text files - /// actual object have ObjectTemplate as member "appearance" - std::vector objects; - - /// reads one of H3 text files that contain object templates description - void readTextFile(std::string path); -public: - - CDefObjInfoHandler(); - - /// Erases all templates with given type/subtype - void eraseAll(Obj type, si32 subtype); - - /// Add new template into the list - void registerTemplate(ObjectTemplate obj); - - /// picks all possible candidates for specific pair - std::vector pickCandidates(Obj type, si32 subtype) const; - /// picks all candidates for and of possible - also filters them by terrain - std::vector pickCandidates(Obj type, si32 subtype, ETerrainType terrain) const; - /// as above, but also filters out templates that are not applicable according to accepted test - std::vector pickCandidates(Obj type, si32 subtype, ETerrainType terrain, std::function filter) const; - - template void serialize(Handler &h, const int version) - { - h & objects; - } -}; +#pragma once + +#include "../GameConstants.h" + +/* + * ObjectTemplate.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 + * + */ + +class CBinaryReader; +class CLegacyConfigParser; +class JsonNode; + +class DLL_LINKAGE ObjectTemplate +{ + enum EBlockMapBits + { + VISIBLE = 1, + VISITABLE = 2, + BLOCKED = 4 + }; + + /// tiles that are covered by this object, uses EBlockMapBits enum as flags + std::vector> usedTiles; + /// directions from which object can be entered, format same as for moveDir in CGHeroInstance(but 0 - 7) + ui8 visitDir; + /// list of terrains on which this object can be placed + std::set allowedTerrains; + +public: + /// H3 ID/subID of this object + Obj id; + si32 subid; + /// print priority, objects with higher priority will be print first, below everything else + si32 printPriority; + /// animation file that should be used to display object + std::string animationFile; + + /// string ID, equals to def base name for h3m files (lower case, no extension) or specified in mod data + std::string stringID; + + ui32 getWidth() const; + ui32 getHeight() const; + void setSize(ui32 width, ui32 height); + + bool isVisitable() const; + + // Checks object used tiles + // Position is relative to bottom-right corner of the object, can not be negative + bool isWithin(si32 X, si32 Y) const; + bool isVisitableAt(si32 X, si32 Y) const; + bool isVisibleAt(si32 X, si32 Y) const; + bool isBlockedAt(si32 X, si32 Y) const; + + // Checks if object is visitable from certain direction. X and Y must be between -1..+1 + bool isVisitableFrom(si8 X, si8 Y) const; + + // Checks if object can be placed on specific terrain + bool canBePlacedAt(ETerrainType terrain) const; + + ObjectTemplate(); + + void readTxt(CLegacyConfigParser & parser); + void readMsk(); + void readMap(CBinaryReader & reader); + void readJson(const JsonNode & node); + + template void serialize(Handler &h, const int version) + { + h & usedTiles & allowedTerrains & animationFile & stringID; + h & id & subid & printPriority & visitDir; + } +}; + diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index 864609c36..9935ed153 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -10,7 +10,7 @@ #include "../CGeneralTextHandler.h" #include "../StartInfo.h" #include "../CArtHandler.h" //for hero crossover -#include "../CObjectHandler.h" //for hero crossover +#include "../mapObjects/CGHeroInstance.h"//for hero crossover #include "../CHeroHandler.h" #include "CMapService.h" #include "CMap.h" diff --git a/lib/mapping/CCampaignHandler.h b/lib/mapping/CCampaignHandler.h index e1d72a359..23f894d77 100644 --- a/lib/mapping/CCampaignHandler.h +++ b/lib/mapping/CCampaignHandler.h @@ -1,6 +1,5 @@ #pragma once -#include "../../Global.h" #include "../../lib/GameConstants.h" /* diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 2231fd5f2..15e66c26e 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -6,7 +6,8 @@ #include "../CCreatureHandler.h" #include "../CTownHandler.h" #include "../CHeroHandler.h" -#include "../CDefObjInfoHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" +#include "../mapObjects/CGHeroInstance.h" #include "../CGeneralTextHandler.h" #include "../CSpellHandler.h" #include "CMapEditManager.h" @@ -333,72 +334,72 @@ bool CMap::isWaterTile(const int3 &pos) const return isInTheMap(pos) && getTile(pos).terType == ETerrainType::WATER; } -bool CMap::checkForVisitableDir(const int3 & src, const TerrainTile *pom, const int3 & dst ) const -{ - for(ui32 b=0; bvisitableObjects.size(); ++b) //checking destination tile - { - if(!vstd::contains(pom->blockingObjects, pom->visitableObjects[b])) //this visitable object is not blocking, ignore - continue; - - const CGObjectInstance * obj = pom->visitableObjects[b]; - - if (!obj->appearance.isVisitableFrom(src.x - dst.x, src.y - dst.y)) - return false; - } - return true; +bool CMap::checkForVisitableDir(const int3 & src, const TerrainTile *pom, const int3 & dst ) const +{ + for(ui32 b=0; bvisitableObjects.size(); ++b) //checking destination tile + { + if(!vstd::contains(pom->blockingObjects, pom->visitableObjects[b])) //this visitable object is not blocking, ignore + continue; + + const CGObjectInstance * obj = pom->visitableObjects[b]; + + if (!obj->appearance.isVisitableFrom(src.x - dst.x, src.y - dst.y)) + return false; + } + return true; } int3 CMap::guardingCreaturePosition (int3 pos) const -{ - - const int3 originalPos = pos; - // Give monster at position priority. - if (!isInTheMap(pos)) - return int3(-1, -1, -1); - const TerrainTile &posTile = getTile(pos); - if (posTile.visitable) - { - for (CGObjectInstance* obj : posTile.visitableObjects) - { - if(obj->blockVisit) - { - if (obj->ID == Obj::MONSTER) // Monster - return pos; - else - return int3(-1, -1, -1); //blockvis objects are not guarded by neighbouring creatures - } - } - } - - // See if there are any monsters adjacent. - bool water = posTile.isWater(); - - pos -= int3(1, 1, 0); // Start with top left. - for (int dx = 0; dx < 3; dx++) - { - for (int dy = 0; dy < 3; dy++) - { - if (isInTheMap(pos)) - { - const auto & tile = getTile(pos); - if (tile.visitable && (tile.isWater() == water)) - { - for (CGObjectInstance* obj : tile.visitableObjects) - { - if (obj->ID == Obj::MONSTER && checkForVisitableDir(pos, &posTile, originalPos)) // Monster being able to attack investigated tile - { - return pos; - } - } - } - } - - pos.y++; - } - pos.y -= 3; - pos.x++; - } - +{ + + const int3 originalPos = pos; + // Give monster at position priority. + if (!isInTheMap(pos)) + return int3(-1, -1, -1); + const TerrainTile &posTile = getTile(pos); + if (posTile.visitable) + { + for (CGObjectInstance* obj : posTile.visitableObjects) + { + if(obj->blockVisit) + { + if (obj->ID == Obj::MONSTER) // Monster + return pos; + else + return int3(-1, -1, -1); //blockvis objects are not guarded by neighbouring creatures + } + } + } + + // See if there are any monsters adjacent. + bool water = posTile.isWater(); + + pos -= int3(1, 1, 0); // Start with top left. + for (int dx = 0; dx < 3; dx++) + { + for (int dy = 0; dy < 3; dy++) + { + if (isInTheMap(pos)) + { + const auto & tile = getTile(pos); + if (tile.visitable && (tile.isWater() == water)) + { + for (CGObjectInstance* obj : tile.visitableObjects) + { + if (obj->ID == Obj::MONSTER && checkForVisitableDir(pos, &posTile, originalPos)) // Monster being able to attack investigated tile + { + return pos; + } + } + } + } + + pos.y++; + } + pos.y -= 3; + pos.x++; + } + return int3(-1, -1, -1); } diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index f71c4df06..44223539d 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -12,7 +12,9 @@ #pragma once #include "../ConstTransitivePtr.h" -#include "../CObjectHandler.h" +#include "../mapObjects/MiscObjects.h" // To serialize static props +#include "../mapObjects/CQuest.h" // To serialize static props +#include "../mapObjects/CGTownInstance.h" // To serialize static props #include "../ResourceSet.h" #include "../int3.h" #include "../GameConstants.h" diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index e4d079b03..a778dbad1 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -3,7 +3,8 @@ #include "../JsonNode.h" #include "../filesystem/Filesystem.h" -#include "../CDefObjInfoHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" +#include "../mapObjects/CGHeroInstance.h" #include "../VCMI_Lib.h" MapRect::MapRect() : x(0), y(0), z(0), width(0), height(0) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index d95d96369..f064cc70a 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -20,8 +20,8 @@ #include "../CCreatureHandler.h" #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" -#include "../CObjectHandler.h" -#include "../CDefObjInfoHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" +#include "../mapObjects/MapObjects.h" #include "../VCMI_Lib.h" #include "../NetPacksBase.h" @@ -1569,7 +1569,7 @@ void CMapLoaderH3M::readObjects() { if(objTempl.subid == 0) { - nobj = new CGPyramid(); + nobj = new CBank(); } else { diff --git a/lib/mapping/MapFormatH3M.h b/lib/mapping/MapFormatH3M.h index 6a44449e0..dd2bc5be5 100644 --- a/lib/mapping/MapFormatH3M.h +++ b/lib/mapping/MapFormatH3M.h @@ -14,7 +14,7 @@ #include "CMapService.h" #include "../GameConstants.h" #include "../ResourceSet.h" -#include "../CDefObjInfoHandler.h" +#include "../mapObjects/ObjectTemplate.h" #include "../int3.h" diff --git a/lib/registerTypes/RegisterTypes.cpp b/lib/registerTypes/RegisterTypes.cpp index 390fc320d..123089b45 100644 --- a/lib/registerTypes/RegisterTypes.cpp +++ b/lib/registerTypes/RegisterTypes.cpp @@ -8,7 +8,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -17,7 +17,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" // For reference: peak memory usage by gcc during compilation of register type templates // registerTypesMapObjects: 1.9 Gb diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 7836bea19..dd7467108 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -4,11 +4,14 @@ #include "../NetPacks.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" -#include "../CObjectHandler.h" #include "../CGameState.h" #include "../CHeroHandler.h" #include "../CTownHandler.h" #include "../CModHandler.h" //needed? +#include "../mapObjects/CObjectClassesHandler.h" +#include "../mapObjects/CRewardableConstructor.h" +#include "../mapObjects/CommonConstructors.h" +#include "../mapObjects/MapObjects.h" /* * RegisterTypes.h, part of VCMI engine @@ -32,7 +35,6 @@ void registerTypesMapObjects1(Serializer &s) // Non-armed objects s.template registerType(); - s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); @@ -67,11 +69,66 @@ void registerTypesMapObjects1(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); - s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); } +template +void registerTypesMapObjectTypes(Serializer &s) +{ + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + +#define REGISTER_GENERIC_HANDLER(TYPENAME) s.template registerType >() + + REGISTER_GENERIC_HANDLER(CGObjectInstance); + REGISTER_GENERIC_HANDLER(CGMarket); + REGISTER_GENERIC_HANDLER(CCartographer); + REGISTER_GENERIC_HANDLER(CGArtifact); + REGISTER_GENERIC_HANDLER(CGBlackMarket); + REGISTER_GENERIC_HANDLER(CGBoat); + REGISTER_GENERIC_HANDLER(CGBonusingObject); + REGISTER_GENERIC_HANDLER(CGBorderGate); + REGISTER_GENERIC_HANDLER(CGBorderGuard); + REGISTER_GENERIC_HANDLER(CGCreature); + REGISTER_GENERIC_HANDLER(CGDenOfthieves); + REGISTER_GENERIC_HANDLER(CGEvent); + REGISTER_GENERIC_HANDLER(CGGarrison); + REGISTER_GENERIC_HANDLER(CGHeroPlaceholder); + REGISTER_GENERIC_HANDLER(CGHeroInstance); + REGISTER_GENERIC_HANDLER(CGKeymasterTent); + REGISTER_GENERIC_HANDLER(CGLighthouse); + REGISTER_GENERIC_HANDLER(CGMagi); + REGISTER_GENERIC_HANDLER(CGMagicSpring); + REGISTER_GENERIC_HANDLER(CGMagicWell); + REGISTER_GENERIC_HANDLER(CGMarket); + REGISTER_GENERIC_HANDLER(CGMine); + REGISTER_GENERIC_HANDLER(CGObelisk); + REGISTER_GENERIC_HANDLER(CGObservatory); + REGISTER_GENERIC_HANDLER(CGOnceVisitable); + REGISTER_GENERIC_HANDLER(CGPandoraBox); + REGISTER_GENERIC_HANDLER(CGPickable); + REGISTER_GENERIC_HANDLER(CGQuestGuard); + REGISTER_GENERIC_HANDLER(CGResource); + REGISTER_GENERIC_HANDLER(CGScholar); + REGISTER_GENERIC_HANDLER(CGSeerHut); + REGISTER_GENERIC_HANDLER(CGShipyard); + REGISTER_GENERIC_HANDLER(CGShrine); + REGISTER_GENERIC_HANDLER(CGSignBottle); + REGISTER_GENERIC_HANDLER(CGSirens); + REGISTER_GENERIC_HANDLER(CGTeleport); + REGISTER_GENERIC_HANDLER(CGUniversity); + REGISTER_GENERIC_HANDLER(CGVisitableOPH); + REGISTER_GENERIC_HANDLER(CGVisitableOPW); + REGISTER_GENERIC_HANDLER(CGWitchHut); + +#undef REGISTER_GENERIC_HANDLER +} + template void registerTypesMapObjects2(Serializer &s) { @@ -80,16 +137,16 @@ void registerTypesMapObjects2(Serializer &s) s.template registerType(); s.template registerType(); - - s.template registerType(); - - s.template registerType(); - s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); - s.template registerType(); s.template registerType(); s.template registerType(); @@ -188,6 +245,7 @@ void registerTypesClientPacks1(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); } template @@ -301,6 +359,7 @@ void registerTypes(Serializer &s) { registerTypesMapObjects1(s); registerTypesMapObjects2(s); + registerTypesMapObjectTypes(s); registerTypesClientPacks1(s); registerTypesClientPacks2(s); registerTypesServerPacks(s); diff --git a/lib/registerTypes/TypesClientPacks1.cpp b/lib/registerTypes/TypesClientPacks1.cpp index 97e461493..d7c25e297 100644 --- a/lib/registerTypes/TypesClientPacks1.cpp +++ b/lib/registerTypes/TypesClientPacks1.cpp @@ -7,7 +7,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" template void registerTypesClientPacks1>(CISer& s); diff --git a/lib/registerTypes/TypesClientPacks2.cpp b/lib/registerTypes/TypesClientPacks2.cpp index be4b656b5..9773e7787 100644 --- a/lib/registerTypes/TypesClientPacks2.cpp +++ b/lib/registerTypes/TypesClientPacks2.cpp @@ -7,7 +7,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" template void registerTypesClientPacks2>(CISer& s); diff --git a/lib/registerTypes/TypesMapObjects1.cpp b/lib/registerTypes/TypesMapObjects1.cpp index 726ce6092..961a16136 100644 --- a/lib/registerTypes/TypesMapObjects1.cpp +++ b/lib/registerTypes/TypesMapObjects1.cpp @@ -7,7 +7,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -16,8 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" - +#include "../mapObjects/CObjectClassesHandler.h" template void registerTypesMapObjects1>(CISer& s); template void registerTypesMapObjects1>(COSer& s); diff --git a/lib/registerTypes/TypesMapObjects2.cpp b/lib/registerTypes/TypesMapObjects2.cpp index d77ed5822..ab49b6698 100644 --- a/lib/registerTypes/TypesMapObjects2.cpp +++ b/lib/registerTypes/TypesMapObjects2.cpp @@ -7,7 +7,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" template void registerTypesMapObjects2>(CISer& s); diff --git a/lib/registerTypes/TypesMapObjects3.cpp b/lib/registerTypes/TypesMapObjects3.cpp new file mode 100644 index 000000000..83cd20827 --- /dev/null +++ b/lib/registerTypes/TypesMapObjects3.cpp @@ -0,0 +1,28 @@ +#include "StdInc.h" +#include "RegisterTypes.h" + +#include "../mapping/CMapInfo.h" +#include "../StartInfo.h" +#include "../BattleState.h" +#include "../CGameState.h" +#include "../mapping/CMap.h" +#include "../CModHandler.h" +#include "../mapObjects/CObjectHandler.h" +#include "../CCreatureHandler.h" +#include "../VCMI_Lib.h" +#include "../CArtHandler.h" +#include "../CHeroHandler.h" +#include "../CSpellHandler.h" +#include "../CTownHandler.h" +#include "../mapping/CCampaignHandler.h" +#include "../NetPacks.h" +#include "../mapObjects/CObjectClassesHandler.h" + +template void registerTypesMapObjectTypes>(CISer& s); +template void registerTypesMapObjectTypes>(COSer& s); +template void registerTypesMapObjectTypes>(CISer& s); +template void registerTypesMapObjectTypes>(COSer& s); +template void registerTypesMapObjectTypes(CSaveFile & s); +template void registerTypesMapObjectTypes(CLoadFile & s); +template void registerTypesMapObjectTypes(CTypeList & s); +template void registerTypesMapObjectTypes(CLoadIntegrityValidator & s); diff --git a/lib/registerTypes/TypesPregamePacks.cpp b/lib/registerTypes/TypesPregamePacks.cpp index 1fd97e76b..08ead1777 100644 --- a/lib/registerTypes/TypesPregamePacks.cpp +++ b/lib/registerTypes/TypesPregamePacks.cpp @@ -7,7 +7,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" template void registerTypesPregamePacks>(CISer& s); template void registerTypesPregamePacks>(COSer& s); diff --git a/lib/registerTypes/TypesServerPacks.cpp b/lib/registerTypes/TypesServerPacks.cpp index caabdcd1e..306078e92 100644 --- a/lib/registerTypes/TypesServerPacks.cpp +++ b/lib/registerTypes/TypesServerPacks.cpp @@ -7,7 +7,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" template void registerTypesServerPacks>(CISer& s); template void registerTypesServerPacks>(COSer& s); diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index 88a9c1a1c..c1f26e443 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -216,12 +216,16 @@ void CMapGenOptions::finalize(CRandomGenerator & rand) if(waterContent == EWaterContent::RANDOM) { - waterContent = static_cast(rand.nextInt(EWaterContent::LAST_ITEM)); + waterContent = static_cast(rand.nextInt(EWaterContent::NONE, EWaterContent::ISLANDS)); } if(monsterStrength == EMonsterStrength::RANDOM) { - monsterStrength = static_cast(rand.nextInt(EMonsterStrength::LAST_ITEM)); + monsterStrength = static_cast(rand.nextInt(EMonsterStrength::GLOBAL_WEAK, EMonsterStrength::GLOBAL_STRONG)); } + + //rectangular maps are the future of gaming + //setHeight(20); + //setWidth(50); } void CMapGenOptions::updatePlayers() diff --git a/lib/rmg/CMapGenOptions.h b/lib/rmg/CMapGenOptions.h index 6757ac436..4829fa00d 100644 --- a/lib/rmg/CMapGenOptions.h +++ b/lib/rmg/CMapGenOptions.h @@ -23,8 +23,7 @@ namespace EWaterContent RANDOM = -1, NONE, NORMAL, - ISLANDS, - LAST_ITEM = ISLANDS + ISLANDS }; } @@ -32,11 +31,13 @@ namespace EMonsterStrength { enum EMonsterStrength { - RANDOM = -1, - WEAK, - NORMAL, - STRONG, - LAST_ITEM = STRONG + RANDOM = -2, + ZONE_WEAK = -1, + ZONE_NORMAL = 0, + ZONE_STRONG = 1, + GLOBAL_WEAK = 2, + GLOBAL_NORMAL = 3, + GLOBAL_STRONG = 4 }; } diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 4e51b3e6e..79324a684 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -5,8 +5,6 @@ #include "../VCMI_Lib.h" #include "../CGeneralTextHandler.h" #include "../mapping/CMapEditManager.h" -#include "../CObjectHandler.h" -#include "../CDefObjInfoHandler.h" #include "../CTownHandler.h" #include "../StringConstants.h" #include "../filesystem/Filesystem.h" @@ -234,20 +232,12 @@ void CMapGenerator::createConnections() //rearrange tiles in random order auto tilesCopy = zoneA->getTileInfo(); std::vector tiles(tilesCopy.begin(), tilesCopy.end()); - //TODO: hwo to use std::shuffle with our generator? - //std::random_shuffle (tiles.begin(), tiles.end(), &gen->rand.nextInt); - - int i, n; - n = (tiles.end() - tiles.begin()); - for (i=n-1; i>0; --i) - { - std::swap (tiles.begin()[i],tiles.begin()[rand.nextInt(i+1)]); - } + RandomGeneratorUtil::randomShuffle(tiles, rand); int3 guardPos(-1,-1,-1); auto otherZoneTiles = zoneB->getTileInfo(); - auto otherZoneCenter = zoneB->getPos(); + //auto otherZoneCenter = zoneB->getPos(); for (auto tile : tiles) { @@ -258,6 +248,7 @@ void CMapGenerator::createConnections() }); if (guardPos.valid()) { + setOccupied (guardPos, ETileType::FREE); //just in case monster is too weak to spawn zoneA->addMonster (this, guardPos, connection.getGuardStrength()); //TODO: set value according to template //zones can make paths only in their own area zoneA->crunchPath (this, guardPos, zoneA->getPos(), zoneA->getId()); //make connection towards our zone center diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index d802e00ad..9b56c11d2 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -15,7 +15,6 @@ #include "../CRandomGenerator.h" #include "CMapGenOptions.h" #include "CRmgTemplateZone.h" -#include "../CObjectHandler.h" #include "../int3.h" class CMap; diff --git a/lib/rmg/CRmgTemplateStorage.cpp b/lib/rmg/CRmgTemplateStorage.cpp index ec91e6304..49f18d396 100644 --- a/lib/rmg/CRmgTemplateStorage.cpp +++ b/lib/rmg/CRmgTemplateStorage.cpp @@ -60,14 +60,105 @@ void CJsonRmgTemplateLoader::loadTemplates() zone->setPlayerTowns(parseTemplateZoneTowns(zoneNode["playerTowns"])); zone->setNeutralTowns(parseTemplateZoneTowns(zoneNode["neutralTowns"])); zone->setTownTypes(parseTownTypes(zoneNode["townTypes"].Vector(), zone->getDefaultTownTypes())); - zone->setMatchTerrainToTown(zoneNode["matchTerrainToTown"].Bool()); + if (!zoneNode["matchTerrainToTown"].isNull()) //default : true + zone->setMatchTerrainToTown(zoneNode["matchTerrainToTown"].Bool()); zone->setTerrainTypes(parseTerrainTypes(zoneNode["terrainTypes"].Vector(), zone->getDefaultTerrainTypes())); zone->setTownsAreSameType((zoneNode["townsAreSameType"].Bool())); - if(!zoneNode["terrainTypeLikeZone"].isNull()) zone->setTerrainTypeLikeZone(boost::lexical_cast(zoneNode["terrainTypeLikeZone"].String())); - if(!zoneNode["townTypeLikeZone"].isNull()) zone->setTownTypeLikeZone(boost::lexical_cast(zoneNode["townTypeLikeZone"].String())); + + const std::string monsterStrength = zoneNode["monsters"].String(); + if (monsterStrength == "weak") + zone->setMonsterStrength(EMonsterStrength::ZONE_WEAK); + else if (monsterStrength == "normal") + zone->setMonsterStrength(EMonsterStrength::ZONE_NORMAL); + else if (monsterStrength == "strong") + zone->setMonsterStrength(EMonsterStrength::ZONE_STRONG); + else + throw (rmgException("incorrect monster power")); + + if (!zoneNode["mines"].isNull()) + { + auto mines = zoneNode["mines"].Struct(); + //FIXME: maybe there is a smarter way to parse it already? + zone->setMinesAmount(Res::WOOD, mines["wood"].Float()); + zone->setMinesAmount(Res::ORE, mines["ore"].Float()); + zone->setMinesAmount(Res::GEMS, mines["gems"].Float()); + zone->setMinesAmount(Res::CRYSTAL, mines["crystal"].Float()); + zone->setMinesAmount(Res::SULFUR, mines["sulfur"].Float()); + zone->setMinesAmount(Res::MERCURY, mines["mercury"].Float()); + zone->setMinesAmount(Res::GOLD, mines["gold"].Float()); + //TODO: Mithril + } + + //treasures + if (!zoneNode["treasure"].isNull()) + { + int totalDensity = 0; + //TODO: parse vector of different treasure settings + if (zoneNode["treasure"].getType() == JsonNode::DATA_STRUCT) + { + auto treasureInfo = zoneNode["treasure"].Struct(); + { + CTreasureInfo ti; + ti.min = treasureInfo["min"].Float(); + ti.max = treasureInfo["max"].Float(); + ti.density = treasureInfo["density"].Float(); //TODO: use me + totalDensity += ti.density; + ti.threshold = totalDensity; + zone->addTreasureInfo(ti); + } + } + else if (zoneNode["treasure"].getType() == JsonNode::DATA_VECTOR) + { + for (auto treasureInfo : zoneNode["treasure"].Vector()) + { + CTreasureInfo ti; + ti.min = treasureInfo["min"].Float(); + ti.max = treasureInfo["max"].Float(); + ti.density = treasureInfo["density"].Float(); + totalDensity += ti.density; + ti.threshold = totalDensity; + zone->addTreasureInfo(ti); + } + } + zone->setTotalDensity (totalDensity); + } zones[zone->getId()] = zone; } + + //copy settings from already parsed zones + for (const auto & zonePair : templateNode["zones"].Struct()) + { + auto zoneId = boost::lexical_cast(zonePair.first); + auto zone = zones[zoneId]; + + const auto & zoneNode = zonePair.second; + + if (!zoneNode["terrainTypeLikeZone"].isNull()) + zone->setTerrainTypes (zones[zoneNode["terrainTypeLikeZone"].Float()]->getTerrainTypes()); + + if (!zoneNode["townTypeLikeZone"].isNull()) + zone->setTownTypes (zones[zoneNode["townTypeLikeZone"].Float()]->getTownTypes()); + + if (!zoneNode["treasureLikeZone"].isNull()) + { + for (auto treasureInfo : zones[zoneNode["treasureLikeZone"].Float()]->getTreasureInfo()) + { + zone->addTreasureInfo(treasureInfo); + } + zone->setTotalDensity (zones[zoneNode["treasureLikeZone"].Float()]->getTotalDensity()); + } + + if (!zoneNode["minesLikeZone"].isNull()) + { + for (auto mineInfo : zones[zoneNode["minesLikeZone"].Float()]->getMinesInfo()) + { + zone->setMinesAmount (mineInfo.first, mineInfo.second); + } + + } + } + tpl->setZones(zones); // Parse connections @@ -176,6 +267,9 @@ std::set CJsonRmgTemplateLoader::parseTownTypes(const JsonVector & tow std::set CJsonRmgTemplateLoader::parseTerrainTypes(const JsonVector & terTypeStrings, const std::set & defaultTerrainTypes) const { std::set terTypes; + if (terTypeStrings.empty()) //nothing was specified + return defaultTerrainTypes; + for(const auto & node : terTypeStrings) { const auto & terTypeStr = node.String(); diff --git a/lib/rmg/CRmgTemplateZone.cpp b/lib/rmg/CRmgTemplateZone.cpp index 01f2e09b8..2a1a8a6ae 100644 --- a/lib/rmg/CRmgTemplateZone.cpp +++ b/lib/rmg/CRmgTemplateZone.cpp @@ -17,6 +17,11 @@ #include "../VCMI_Lib.h" #include "../CTownHandler.h" #include "../CCreatureHandler.h" +#include "../CSpellHandler.h" //for choosing random spells + +#include "../mapObjects/CObjectClassesHandler.h" +#include "../mapObjects/CGPandoraBox.h" +#include "../mapObjects/CRewardableObject.h" class CMap; class CMapEditManager; @@ -119,8 +124,16 @@ void CTileInfo::setTerrainType(ETerrainType value) terrain = value; } -CRmgTemplateZone::CRmgTemplateZone() : id(0), type(ETemplateZoneType::PLAYER_START), size(1), - townsAreSameType(false), matchTerrainToTown(true) +CRmgTemplateZone::CRmgTemplateZone() : + id(0), + type(ETemplateZoneType::PLAYER_START), + size(1), + townsAreSameType(false), + matchTerrainToTown(true), + townType(0), + terrainType (ETerrainType::GRASS), + zoneMonsterStrength(EMonsterStrength::ZONE_NORMAL), + totalDensity(0) { townTypes = getDefaultTownTypes(); terrainTypes = getDefaultTerrainTypes(); @@ -247,30 +260,22 @@ void CRmgTemplateZone::setTerrainTypes(const std::set & value) std::set CRmgTemplateZone::getDefaultTerrainTypes() const { std::set terTypes; - static const ETerrainType::EETerrainType allowedTerTypes[] = { ETerrainType::DIRT, ETerrainType::SAND, ETerrainType::GRASS, ETerrainType::SNOW, - ETerrainType::SWAMP, ETerrainType::ROUGH, ETerrainType::SUBTERRANEAN, ETerrainType::LAVA }; - for(auto & allowedTerType : allowedTerTypes) terTypes.insert(allowedTerType); + static const ETerrainType::EETerrainType allowedTerTypes[] = {ETerrainType::DIRT, ETerrainType::SAND, ETerrainType::GRASS, ETerrainType::SNOW, + ETerrainType::SWAMP, ETerrainType::ROUGH, ETerrainType::SUBTERRANEAN, ETerrainType::LAVA}; + for (auto & allowedTerType : allowedTerTypes) + terTypes.insert(allowedTerType); + return terTypes; } -boost::optional CRmgTemplateZone::getTerrainTypeLikeZone() const +void CRmgTemplateZone::setMinesAmount (TResource res, ui16 amount) { - return terrainTypeLikeZone; + mines[res] = amount; } -void CRmgTemplateZone::setTerrainTypeLikeZone(boost::optional value) +std::map CRmgTemplateZone::getMinesInfo() const { - terrainTypeLikeZone = value; -} - -boost::optional CRmgTemplateZone::getTownTypeLikeZone() const -{ - return townTypeLikeZone; -} - -void CRmgTemplateZone::setTownTypeLikeZone(boost::optional value) -{ - townTypeLikeZone = value; + return mines; } void CRmgTemplateZone::addConnection(TRmgTemplateZoneId otherZone) @@ -282,6 +287,33 @@ std::vector CRmgTemplateZone::getConnections() const { return connections; } + +void CRmgTemplateZone::setMonsterStrength (EMonsterStrength::EMonsterStrength val) +{ + assert (vstd::iswithin(val, EMonsterStrength::ZONE_WEAK, EMonsterStrength::ZONE_STRONG)); + zoneMonsterStrength = val; +} + +void CRmgTemplateZone::setTotalDensity (ui16 val) +{ + totalDensity = val; +} + +ui16 CRmgTemplateZone::getTotalDensity () const +{ + return totalDensity; +} + +void CRmgTemplateZone::addTreasureInfo(CTreasureInfo & info) +{ + treasureInfo.push_back(info); +} + +std::vector CRmgTemplateZone::getTreasureInfo() +{ + return treasureInfo; +} + float3 CRmgTemplateZone::getCenter() const { return center; @@ -335,7 +367,78 @@ void CRmgTemplateZone::createBorder(CMapGenerator* gen) } } -bool CRmgTemplateZone::crunchPath (CMapGenerator* gen, const int3 &src, const int3 &dst, TRmgTemplateZoneId zone) +void CRmgTemplateZone::fractalize(CMapGenerator* gen) +{ + std::vector clearedTiles; + std::set possibleTiles; + std::set tilesToClear; //will be set clear + std::set tilesToIgnore; //will be erased in this iteration + + const float minDistance = std::sqrt(totalDensity); + for (auto tile : tileinfo) + { + if (gen->isFree(tile)) + clearedTiles.push_back(tile); + else if (gen->isPossible(tile)) + possibleTiles.insert(tile); + } + if (clearedTiles.empty()) //this should come from zone connections + { + clearedTiles.push_back(pos); //zone center should be always clear + } + + while (possibleTiles.size()) + { + for (auto tileToMakePath : possibleTiles) + { + //find closest free tile + float currentDistance = 1e10; + int3 closestTile (-1,-1,-1); + + for (auto clearTile : clearedTiles) + { + float distance = tileToMakePath.dist2d(clearTile); + + if (distance < currentDistance) + { + currentDistance = distance; + closestTile = clearTile; + } + if (currentDistance <= minDistance) + { + //this tile is close enough. Forget about it and check next one + tilesToIgnore.insert (tileToMakePath); + break; + } + } + //if tiles is not close enough, make path to it + if (currentDistance > minDistance) + { + crunchPath (gen, tileToMakePath, closestTile, id, &tilesToClear); + break; //next iteration - use already cleared tiles + } + } + + for (auto tileToClear : tilesToClear) + { + //move cleared tiles from one set to another + clearedTiles.push_back(tileToClear); + vstd::erase_if_present(possibleTiles, tileToClear); + } + for (auto tileToClear : tilesToIgnore) + { + //these tiles are already connected, ignore them + vstd::erase_if_present(possibleTiles, tileToClear); + } + if (tilesToClear.empty()) //nothing else can be done (?) + break; + tilesToClear.clear(); //empty this container + tilesToIgnore.clear(); + } + logGlobal->infoStream() << boost::format ("Zone %d subdivided fractally") %id; +} + +bool CRmgTemplateZone::crunchPath (CMapGenerator* gen, const int3 &src, const int3 &dst, TRmgTemplateZoneId zone, std::set* clearedTiles) { /* make shortest path with free tiles, reachning dst or closest already free tile. Avoid blocks. @@ -350,10 +453,13 @@ do not leave zone border while (!end) { if (currentPos == dst) + { + result = true; break; + } auto lastDistance = distance; - gen->foreach_neighbour (currentPos, [this, gen, ¤tPos, dst, &distance, &result, &end](int3 &pos) + gen->foreach_neighbour (currentPos, [this, gen, ¤tPos, dst, &distance, &result, &end, clearedTiles](int3 &pos) { if (!result) //not sure if lambda is worth it... { @@ -371,6 +477,8 @@ do not leave zone border if (gen->isPossible(pos)) { gen->setOccupied (pos, ETileType::FREE); + if (clearedTiles) + clearedTiles->insert(pos); currentPos = pos; distance = currentPos.dist2dSQ (dst); } @@ -401,18 +509,50 @@ void CRmgTemplateZone::addRequiredObject(CGObjectInstance * obj, si32 strength) requiredObjects.push_back(std::make_pair(obj, strength)); } -void CRmgTemplateZone::addMonster(CMapGenerator* gen, int3 &pos, si32 strength) +bool CRmgTemplateZone::addMonster(CMapGenerator* gen, int3 &pos, si32 strength) { + //precalculate actual (randomized) monster strength based on this post + //http://forum.vcmi.eu/viewtopic.php?p=12426#12426 + + int mapMonsterStrength = gen->mapGenOptions->getMonsterStrength(); + int monsterStrength = zoneMonsterStrength + mapMonsterStrength - 1; //array index from 0 to 4 + static const int value1[] = {2500, 1500, 1000, 500, 0}; + static const int value2[] = {7500, 7500, 7500, 5000, 5000}; + static const float multiplier1[] = {0.5, 0.75, 1.0, 1.5, 1.5}; + static const float multiplier2[] = {0.5, 0.75, 1.0, 1.0, 1.5}; + + int strength1 = std::max(0.f, (strength - value1[monsterStrength]) * multiplier1[monsterStrength]); + int strength2 = std::max(0.f, (strength - value2[monsterStrength]) * multiplier2[monsterStrength]); + + strength = strength1 + strength2; + if (strength < 2000) + return false; //no guard at all + CreatureID creId = CreatureID::NONE; int amount = 0; - while (true) + std::vector possibleCreatures; + for (auto cre : VLC->creh->creatures) { - creId = VLC->creh->pickRandomMonster(gen->rand); - auto cre = VLC->creh->creatures[creId]; - amount = strength / cre->AIValue; - if (amount >= cre->ammMin && amount <= 100) - break; + if (cre->special) + continue; + if ((cre->AIValue * (cre->ammMin + cre->ammMax) / 2 < strength) && (strength < cre->AIValue * 100)) //at least one full monster. size between minimum size of given stack and 100 + { + possibleCreatures.push_back(cre->idNumber); + } } + if (possibleCreatures.size()) + { + creId = *RandomGeneratorUtil::nextItem(possibleCreatures, gen->rand); + amount = strength / VLC->creh->creatures[creId]->AIValue; + if (amount >= 4) + amount *= gen->rand.nextDouble(0.75, 1.25); + } + else //just pick any available creature + { + creId = CreatureID(132); //Azure Dragon + amount = strength / VLC->creh->creatures[creId]->AIValue; + } + auto guard = new CGCreature(); guard->ID = Obj::MONSTER; @@ -422,71 +562,303 @@ void CRmgTemplateZone::addMonster(CMapGenerator* gen, int3 &pos, si32 strength) guard->putStack(SlotID(0), hlp); placeObject(gen, guard, pos); + return true; } -bool CRmgTemplateZone::fill(CMapGenerator* gen) +bool CRmgTemplateZone::createTreasurePile (CMapGenerator* gen, int3 &pos) { - int townId = 0; + std::map treasures; + std::set boundary; + int3 guardPos (-1,-1,-1); + int3 nextTreasurePos = pos; + + //default values + int maxValue = 5000; + int minValue = 1500; + + if (treasureInfo.size()) + { + //roulette wheel + int r = gen->rand.nextInt (1, totalDensity); + + for (auto t : treasureInfo) + { + if (r <= t.threshold) + { + maxValue = t.max; + minValue = t.min; + break; + } + } + } + + int currentValue = 0; + CGObjectInstance * object = nullptr; + while (currentValue < minValue) + { + //TODO: this works only for 1-tile objects + //make sure our shape is consistent + treasures[nextTreasurePos] = nullptr; + for (auto treasurePos : treasures) + { + gen->foreach_neighbour (treasurePos.first, [gen, &boundary](int3 pos) + { + boundary.insert(pos); + }); + } + for (auto treasurePos : treasures) + { + //leaving only boundary around objects + vstd::erase_if_present (boundary, treasurePos.first); + } + for (auto tile : boundary) + { + //we can't extend boundary anymore + if (!(gen->isBlocked(tile) || gen->isPossible(tile))) + break; + } + + int remaining = maxValue - currentValue; + + auto oi = getRandomObject(gen, remaining); + object = oi.generateObject(); + if (!object) + break; + + currentValue += oi.value; + + treasures[nextTreasurePos] = object; + + //now find place for next object + int3 placeFound(-1,-1,-1); + + //FIXME: find out why teh following code causes crashes + //std::vector boundaryVec(boundary.begin(), boundary.end()); + //RandomGeneratorUtil::randomShuffle(boundaryVec, gen->rand); + //for (auto tile : boundaryVec) + for (auto tile : boundary) + { + if (gen->isPossible(tile)) //we can place new treasure only on possible tile + { + bool here = true; + gen->foreach_neighbour (tile, [gen, &here](int3 pos) + { + if (!(gen->isBlocked(pos) || gen->isPossible(pos))) + here = false; + }); + if (here) + { + placeFound = tile; + break; + } + } + } + if (placeFound.valid()) + nextTreasurePos = placeFound; + } + if (treasures.size()) + { + //find object closest to zone center, then con nect it to the middle of the zone + int3 zoneCenter = getPos(); + int3 closestTile = int3(-1,-1,-1); + float minDistance = 1e10; + for (auto treasure : treasures) + { + if (zoneCenter.dist2d(treasure.first) < minDistance) + { + closestTile = treasure.first; + minDistance = zoneCenter.dist2d(treasure.first); + } + } + assert (closestTile.valid()); + if (!crunchPath (gen, closestTile, getPos(), id)) //make sure pile is connected to the middle of zone + { + for (auto treasure : treasures) + { + if (gen->isPossible(treasure.first)) + gen->setOccupied (treasure.first, ETileType::BLOCKED); + } + return true; + } + + for (auto tile : boundary) //guard must be standing there + { + if (gen->isFree(tile)) //this tile could be already blocked, don't place a monster here + { + guardPos = tile; + break; + } + } + + if (guardPos.valid()) + { + for (auto treasure : treasures) + { + placeObject(gen, treasure.second, treasure.first - treasure.second->getVisitableOffset()); + } + if (addMonster(gen, guardPos, currentValue)) + {//block only if the object is guarded + for (auto tile : boundary) + { + if (gen->isPossible(tile)) + gen->setOccupied (tile, ETileType::BLOCKED); + } + } + } + else //we couldn't make a connection to this location, block it + { + for (auto treasure : treasures) + { + if (gen->isPossible(treasure.first)) + gen->setOccupied (treasure.first, ETileType::BLOCKED); + } + } + + return true; + } + else //we did not place eveyrthing successfully + return false; +} +void CRmgTemplateZone::initTownType (CMapGenerator* gen) +{ + + //FIXME: handle case that this player is not present -> towns should be set to neutral + int totalTowns = 0; if ((type == ETemplateZoneType::CPU_START) || (type == ETemplateZoneType::PLAYER_START)) { + //set zone types to player faction, generate main town logGlobal->infoStream() << "Preparing playing zone"; int player_id = *owner - 1; auto & playerInfo = gen->map->players[player_id]; if (playerInfo.canAnyonePlay()) { PlayerColor player(player_id); + townType = gen->mapGenOptions->getPlayersSettings().find(player)->second.getStartingTown(); + + if(townType == CMapGenOptions::CPlayerSettings::RANDOM_TOWN) + townType = *RandomGeneratorUtil::nextItem(VLC->townh->getAllowedFactions(), gen->rand); // all possible towns, skip neutral + auto town = new CGTownInstance(); town->ID = Obj::TOWN; - townId = gen->mapGenOptions->getPlayersSettings().find(player)->second.getStartingTown(); - if(townId == CMapGenOptions::CPlayerSettings::RANDOM_TOWN) - townId = *RandomGeneratorUtil::nextItem(VLC->townh->getAllowedFactions(), gen->rand); // all possible towns, skip neutral - - town->subID = townId; + town->subID = townType; town->tempOwner = player; town->builtBuildings.insert(BuildingID::FORT); town->builtBuildings.insert(BuildingID::DEFAULT); - - placeObject(gen, town, getPos()); - logGlobal->traceStream() << "Placed object"; + placeObject(gen, town, getPos() + town->getVisitableOffset()); //towns are big objects and should be centered around visitable position + + totalTowns++; logGlobal->traceStream() << "Fill player info " << player_id; - auto & playerInfo = gen->map->players[player_id]; + // Update player info playerInfo.allowedFactions.clear(); - playerInfo.allowedFactions.insert(town->subID); + playerInfo.allowedFactions.insert (townType); playerInfo.hasMainTown = true; playerInfo.posOfMainTown = town->pos - int3(2, 0, 0); playerInfo.generateHeroAtMainTown = true; - //requiredObjects.push_back(town); + //now create actual towns + for (int i = 1; i < playerTowns.getCastleCount(); i++) + { + auto town = new CGTownInstance(); + town->ID = Obj::TOWN; - std::vector required_mines; - required_mines.push_back(Res::ERes::WOOD); - required_mines.push_back(Res::ERes::ORE); + if (townsAreSameType) + town->subID = townType; + else + town->subID = *RandomGeneratorUtil::nextItem(VLC->townh->getAllowedFactions(), gen->rand); //TODO: check allowed town types for this zone - for(const auto res : required_mines) - { - auto mine = new CGMine(); - mine->ID = Obj::MINE; - mine->subID = static_cast(res); - mine->producedResource = res; - mine->producedQuantity = mine->defaultResProduction(); - addRequiredObject(mine); + town->tempOwner = player; + town->builtBuildings.insert(BuildingID::FORT); + town->builtBuildings.insert(BuildingID::DEFAULT); + + addRequiredObject (town); + totalTowns++; } + + for (int i = 0; i < playerTowns.getTownCount(); i++) + { + auto town = new CGTownInstance(); + town->ID = Obj::TOWN; + + if (townsAreSameType) + town->subID = townType; + else + town->subID = *RandomGeneratorUtil::nextItem(VLC->townh->getAllowedFactions(), gen->rand); //TODO: check allowed town types for this zone + + town->tempOwner = player; + town->builtBuildings.insert(BuildingID::DEFAULT); + + addRequiredObject (town); + totalTowns++; + } + + //requiredObjects.push_back(town); } else { type = ETemplateZoneType::TREASURE; - townId = *RandomGeneratorUtil::nextItem(VLC->townh->getAllowedFactions(), gen->rand); + townType = *RandomGeneratorUtil::nextItem(VLC->townh->getAllowedFactions(), gen->rand); logGlobal->infoStream() << "Skipping this zone cause no player"; } } else //no player { - townId = *RandomGeneratorUtil::nextItem(VLC->townh->getAllowedFactions(), gen->rand); + townType = *RandomGeneratorUtil::nextItem(VLC->townh->getAllowedFactions(), gen->rand); } + + for (int i = 0; i < neutralTowns.getCastleCount(); i++) + { + auto town = new CGTownInstance(); + town->ID = Obj::TOWN; + + if (townsAreSameType || totalTowns == 0) //first town must match zone type + town->subID = townType; + else + town->subID = *RandomGeneratorUtil::nextItem(VLC->townh->getAllowedFactions(), gen->rand); //TODO: check allowed town types for this zone + + town->tempOwner = PlayerColor::NEUTRAL; + town->builtBuildings.insert(BuildingID::FORT); + town->builtBuildings.insert(BuildingID::DEFAULT); + + if (!totalTowns) //first town in zone goes in the middle + placeObject(gen, town, getPos() + town->getVisitableOffset()); + else + addRequiredObject (town); + totalTowns++; + } + + for (int i = 0; i < neutralTowns.getTownCount(); i++) + { + auto town = new CGTownInstance(); + town->ID = Obj::TOWN; + + if (townsAreSameType || totalTowns == 0) + town->subID = townType; + else + town->subID = *RandomGeneratorUtil::nextItem(VLC->townh->getAllowedFactions(), gen->rand); //TODO: check allowed town types for this zone + + town->tempOwner = PlayerColor::NEUTRAL; + town->builtBuildings.insert(BuildingID::DEFAULT); + + if (!totalTowns) + placeObject(gen, town, getPos() + town->getVisitableOffset()); + else + addRequiredObject (town); + totalTowns++; + } +} + +void CRmgTemplateZone::initTerrainType (CMapGenerator* gen) +{ + + if (matchTerrainToTown) + terrainType = VLC->townh->factions[townType]->nativeTerrain; + else + terrainType = *RandomGeneratorUtil::nextItem(terrainTypes, gen->rand); + //paint zone with matching terrain std::vector tiles; for (auto tile : tileinfo) @@ -494,8 +866,60 @@ bool CRmgTemplateZone::fill(CMapGenerator* gen) tiles.push_back (tile); } gen->editManager->getTerrainSelection().setSelection(tiles); - gen->editManager->drawTerrain(VLC->townh->factions[townId]->nativeTerrain, &gen->rand); + gen->editManager->drawTerrain(terrainType, &gen->rand); +} +bool CRmgTemplateZone::placeMines (CMapGenerator* gen) +{ + std::vector required_mines; + required_mines.push_back(Res::ERes::WOOD); + required_mines.push_back(Res::ERes::ORE); + + static const Res::ERes woodOre[] = {Res::ERes::WOOD, Res::ERes::ORE}; + static const Res::ERes preciousResources[] = {Res::ERes::GEMS, Res::ERes::CRYSTAL, Res::ERes::MERCURY, Res::ERes::SULFUR}; + + + //TODO: factory / copy constructor? + for (const auto & res : woodOre) + { + //TODO: these 2 should be close to town (within 12 tiles radius) + for (int i = 0; i < mines[res]; i++) + { + auto mine = new CGMine(); + mine->ID = Obj::MINE; + mine->subID = static_cast(res); + mine->producedResource = res; + mine->producedQuantity = mine->defaultResProduction(); + addRequiredObject(mine, 1500); + } + } + for (const auto & res : preciousResources) + { + for (int i = 0; i < mines[res]; i++) + { + auto mine = new CGMine(); + mine->ID = Obj::MINE; + mine->subID = static_cast(res); + mine->producedResource = res; + mine->producedQuantity = mine->defaultResProduction(); + addRequiredObject(mine, 3500); + } + } + for (int i = 0; i < mines[Res::GOLD]; i++) + { + auto mine = new CGMine(); + mine->ID = Obj::MINE; + mine->subID = static_cast(Res::GOLD); + mine->producedResource = Res::GOLD; + mine->producedQuantity = mine->defaultResProduction(); + addRequiredObject(mine, 7000); + } + + return true; +} + +bool CRmgTemplateZone::createRequiredObjects(CMapGenerator* gen) +{ logGlobal->infoStream() << "Creating required objects"; for(const auto &obj : requiredObjects) { @@ -510,43 +934,29 @@ bool CRmgTemplateZone::fill(CMapGenerator* gen) logGlobal->traceStream() << "Place found"; placeObject(gen, obj.first, pos); - if (obj.second) - { - guardObject (gen, obj.first, obj.second); - } + guardObject (gen, obj.first, obj.second); } - std::vector guarded_objects; - static auto res_gen = gen->rand.getIntRange(Res::ERes::WOOD, Res::ERes::GOLD); - const double res_mindist = 5; + return true; +} + +void CRmgTemplateZone::createTreasures(CMapGenerator* gen) +{ + const double minDistance = 3; + do { - auto obj = new CGResource(); - auto restype = static_cast(res_gen()); - obj->ID = Obj::RESOURCE; - obj->subID = static_cast(restype); - obj->amount = 0; int3 pos; - if ( ! findPlaceForObject(gen, obj, res_mindist, pos)) + if ( ! findPlaceForTreasurePile(gen, minDistance, pos)) { - delete obj; break; } - placeObject(gen, obj, pos); + createTreasurePile (gen, pos); - if ((restype != Res::ERes::WOOD) && (restype != Res::ERes::ORE)) - { - guarded_objects.push_back(obj); - } } while(true); +} - for(const auto &obj : guarded_objects) - { - if ( ! guardObject(gen, obj, 1000)) - { - //TODO, DEL obj from map - } - } - +void CRmgTemplateZone::createObstacles(CMapGenerator* gen) +{ auto sel = gen->editManager->getTerrainSelection(); sel.clearSelection(); for (auto tile : tileinfo) @@ -563,18 +973,65 @@ bool CRmgTemplateZone::fill(CMapGenerator* gen) placeObject(gen, obj, tile); } } - //logGlobal->infoStream() << boost::format("Filling %d with ROCK") % sel.getSelectedItems().size(); - //gen->editManager->drawTerrain(ETerrainType::ROCK, &gen->gen); +} + +bool CRmgTemplateZone::fill(CMapGenerator* gen) +{ + addAllPossibleObjects (gen); + initTownType(gen); + initTerrainType(gen); + placeMines(gen); + createRequiredObjects(gen); + fractalize(gen); //after required objects are created and linked with their own paths + createTreasures(gen); + createObstacles(gen); + logGlobal->infoStream() << boost::format ("Zone %d filled successfully") %id; return true; } +bool CRmgTemplateZone::findPlaceForTreasurePile(CMapGenerator* gen, si32 min_dist, int3 &pos) +{ + //si32 min_dist = sqrt(tileinfo.size()/density); + int best_distance = 0; + bool result = false; + + //logGlobal->infoStream() << boost::format("Min dist for density %f is %d") % density % min_dist; + for(auto tile : tileinfo) + { + auto dist = gen->getTile(tile).getNearestObjectDistance(); + + if (gen->isPossible(tile) && (dist >= min_dist) && (dist > best_distance)) + { + bool allTilesAvailable = true; + gen->foreach_neighbour (tile, [&gen, &allTilesAvailable](int3 neighbour) + { + if (!(gen->isPossible(neighbour) || gen->isBlocked(neighbour))) + { + allTilesAvailable = false; //all present tiles must be already blocked or ready for new objects + } + }); + if (allTilesAvailable) + { + best_distance = dist; + pos = tile; + result = true; + } + } + } + if (result) + { + gen->setOccupied(pos, ETileType::BLOCKED); //block that tile + } + return result; +} + bool CRmgTemplateZone::findPlaceForObject(CMapGenerator* gen, CGObjectInstance* obj, si32 min_dist, int3 &pos) { //we need object apperance to deduce free tiles if (obj->appearance.id == Obj::NO_OBJ) { - auto templates = VLC->dobjinfo->pickCandidates(obj->ID, obj->subID, gen->map->getTile(getPos()).terType); + auto templates = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID)->getTemplates(gen->map->getTile(getPos()).terType); if (templates.empty()) throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") %obj->ID %obj->subID %pos)); @@ -584,21 +1041,44 @@ bool CRmgTemplateZone::findPlaceForObject(CMapGenerator* gen, CGObjectInstance* //si32 min_dist = sqrt(tileinfo.size()/density); int best_distance = 0; bool result = false; - si32 w = gen->map->width; - si32 h = gen->map->height; + //si32 w = gen->map->width; + //si32 h = gen->map->height; //logGlobal->infoStream() << boost::format("Min dist for density %f is %d") % density % min_dist; - for(auto tile : tileinfo) + + auto tilesBlockedByObject = obj->getBlockedOffsets(); + + for (auto tile : tileinfo) { + //object must be accessible from at least one surounding tile + bool accessible = false; + for (int x = -1; x < 2; x++) + for (int y = -1; y <2; y++) + { + if (x && y) //check only if object is visitable from another tile + { + int3 offset = obj->getVisitableOffset() + int3(x, y, 0); + if (!vstd::contains(tilesBlockedByObject, offset)) + { + int3 nearbyPos = tile + offset; + if (gen->map->isInTheMap(nearbyPos)) + { + if (obj->appearance.isVisitableFrom(x, y) && !gen->isBlocked(nearbyPos)) + accessible = true; + } + } + } + }; + if (!accessible) + continue; + auto ti = gen->getTile(tile); auto dist = ti.getNearestObjectDistance(); //avoid borders - if ((tile.x < 3) || (w - tile.x < 3) || (tile.y < 3) || (h - tile.y < 3)) - continue; if (gen->isPossible(tile) && (dist >= min_dist) && (dist > best_distance)) { bool allTilesAvailable = true; - for (auto blockingTile : obj->getBlockedOffsets()) + for (auto blockingTile : tilesBlockedByObject) { int3 t = tile + blockingTile; if (!gen->map->isInTheMap(t) || !gen->isPossible(t)) @@ -638,7 +1118,7 @@ void CRmgTemplateZone::checkAndPlaceObject(CMapGenerator* gen, CGObjectInstance* if (object->appearance.id == Obj::NO_OBJ) { - auto templates = VLC->dobjinfo->pickCandidates(object->ID, object->subID, gen->map->getTile(pos).terType); + auto templates = VLC->objtypeh->getHandlerFor(object->ID, object->subID)->getTemplates(gen->map->getTile(pos).terType); if (templates.empty()) throw rmgException(boost::to_string(boost::format("Did not find graphics for object (%d,%d) at %s") %object->ID %object->subID %pos)); @@ -647,12 +1127,12 @@ void CRmgTemplateZone::checkAndPlaceObject(CMapGenerator* gen, CGObjectInstance* gen->map->addBlockVisTiles(object); gen->editManager->insertObject(object, pos); - logGlobal->traceStream() << boost::format ("Successfully inserted object (%d,%d) at pos %s") %object->ID %object->subID %pos(); + //logGlobal->traceStream() << boost::format ("Successfully inserted object (%d,%d) at pos %s") %object->ID %object->subID %pos(); } void CRmgTemplateZone::placeObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos) { - logGlobal->traceStream() << boost::format("Inserting object at %d %d") % pos.x % pos.y; + //logGlobal->traceStream() << boost::format("Inserting object at %d %d") % pos.x % pos.y; checkAndPlaceObject (gen, object, pos); @@ -674,30 +1154,308 @@ void CRmgTemplateZone::placeObject(CMapGenerator* gen, CGObjectInstance* object, } } -bool CRmgTemplateZone::guardObject(CMapGenerator* gen, CGObjectInstance* object, si32 str) +std::vector CRmgTemplateZone::getAccessibleOffsets (CMapGenerator* gen, CGObjectInstance* object) { - - logGlobal->traceStream() << boost::format("Guard object at %d %d") % object->pos.x % object->pos.y; + //get all tiles from which this object can be accessed int3 visitable = object->visitablePos(); std::vector tiles; + + auto tilesBlockedByObject = object->getBlockedPos(); //absolue value, as object is already placed + gen->foreach_neighbour(visitable, [&](int3& pos) { - logGlobal->traceStream() << boost::format("Block at %d %d") % pos.x % pos.y; if (gen->isPossible(pos)) { - tiles.push_back(pos); - gen->setOccupied(pos, ETileType::BLOCKED); + if (!vstd::contains(tilesBlockedByObject, pos)) + { + if (object->appearance.isVisitableFrom(pos.x - visitable.x, pos.y - visitable.y) && !gen->isBlocked(pos)) //TODO: refactor - info about visitability from absolute coordinates + { + tiles.push_back(pos); + } + } + }; }); - if ( ! tiles.size()) - { - logGlobal->infoStream() << "Failed"; + + return tiles; +} + +bool CRmgTemplateZone::guardObject(CMapGenerator* gen, CGObjectInstance* object, si32 str) +{ + logGlobal->traceStream() << boost::format("Guard object at %s") % object->pos(); + + std::vector tiles = getAccessibleOffsets (gen, object); + + int3 guardTile(-1,-1,-1); + + for (auto tile : tiles) + { + //crunching path may fail if center of teh zone is dirrectly over wide object + if (crunchPath (gen, tile, getPos(), id)) //make sure object is accessible before surrounding it with blocked tiles + { + guardTile = tile; + break; + } + } + if (!guardTile.valid()) + { + logGlobal->errorStream() << boost::format("Failed to crunch path to object at %s") % object->pos(); return false; } - auto guard_tile = *RandomGeneratorUtil::nextItem(tiles, gen->rand); - gen->setOccupied (guard_tile, ETileType::USED); - addMonster (gen, guard_tile, str); + if (addMonster (gen, guardTile, str)) //do not place obstacles around unguarded object + { + for (auto pos : tiles) + { + if (!gen->isFree(pos)) + gen->setOccupied(pos, ETileType::BLOCKED); + } + + gen->setOccupied (guardTile, ETileType::USED); + } + else + gen->setOccupied (guardTile, ETileType::FREE); return true; } + +ObjectInfo CRmgTemplateZone::getRandomObject (CMapGenerator* gen, ui32 value) +{ + std::vector> tresholds; + ui32 total = 0; + + ui32 minValue = 0.25f * value; + + //roulette wheel + for (auto oi : possibleObjects) + { + if (oi.value >= minValue && oi.value <= value) + { + //TODO: check place for non-removable object + //problem: we need at least template for the object that does not yet exist + total += oi.probability; + tresholds.push_back (std::make_pair (total, oi)); + } + } + + //TODO: generate pandora box with gold if the value is very high + if (tresholds.empty()) + { + ObjectInfo oi; + if (minValue > 20000) //we don't have object valuable enough + { + oi.generateObject = [minValue]() -> CGObjectInstance * + { + auto obj = new CGPandoraBox(); + obj->ID = Obj::PANDORAS_BOX; + obj->subID = 0; + obj->resources[Res::GOLD] = minValue; + return obj; + }; + oi.value = minValue; + oi.probability = 0; + } + else + { + oi.generateObject = [gen]() -> CGObjectInstance * + { + return nullptr; + }; + oi.value = 0; + oi.probability = 0; + } + return oi; + } + + int r = gen->rand.nextInt (1, total); + + for (auto t : tresholds) + { + if (r <= t.first) + return t.second; + } + //FIXME: control reaches end of non-void function. Missing return? +} + +void CRmgTemplateZone::addAllPossibleObjects (CMapGenerator* gen) +{ + //TODO: move typical objects to config + + ObjectInfo oi; + + static const Res::ERes preciousRes[] = {Res::ERes::CRYSTAL, Res::ERes::GEMS, Res::ERes::MERCURY, Res::ERes::SULFUR}; + for (int i = 0; i < 4; i++) + { + oi.generateObject = [i, gen]() -> CGObjectInstance * + { + auto obj = new CGResource(); + obj->ID = Obj::RESOURCE; + obj->subID = static_cast(preciousRes[i]); + obj->amount = 0; + return obj; + }; + oi.value = 1400; + oi.probability = 300; + possibleObjects.push_back (oi); + } + + static const Res::ERes woodOre[] = {Res::ERes::WOOD, Res::ERes::ORE}; + for (int i = 0; i < 2; i++) + { + oi.generateObject = [i, gen]() -> CGObjectInstance * + { + auto obj = new CGResource(); + obj->ID = Obj::RESOURCE; + obj->subID = static_cast(woodOre[i]); + obj->amount = 0; + return obj; + }; + oi.value = 1400; + oi.probability = 300; + possibleObjects.push_back (oi); + } + + oi.generateObject = [gen]() -> CGObjectInstance * + { + auto obj = new CGResource(); + obj->ID = Obj::RESOURCE; + obj->subID = static_cast(Res::ERes::GOLD); + obj->amount = 0; + return obj; + }; + oi.value = 750; + oi.probability = 300; + possibleObjects.push_back (oi); + + oi.generateObject = [gen]() -> CGObjectInstance * + { + auto obj = new CGPickable(); + obj->ID = Obj::TREASURE_CHEST; + obj->subID = 0; + return obj; + }; + oi.value = 1500; + oi.probability = 1000; + possibleObjects.push_back (oi); + + oi.generateObject = [gen]() -> CGObjectInstance * + { + auto obj = new CGArtifact(); + obj->ID = Obj::RANDOM_TREASURE_ART; + obj->subID = 0; + auto a = new CArtifactInstance(); + gen->map->addNewArtifactInstance(a); + obj->storedArtifact = a; + return obj; + }; + oi.value = 2000; + oi.probability = 150; + possibleObjects.push_back (oi); + + oi.generateObject = [gen]() -> CGObjectInstance * + { + auto obj = new CGArtifact(); + obj->ID = Obj::RANDOM_MINOR_ART; + obj->subID = 0; + auto a = new CArtifactInstance(); + gen->map->addNewArtifactInstance(a); + obj->storedArtifact = a; + return obj; + }; + oi.value = 5000; + oi.probability = 150; + possibleObjects.push_back (oi); + + oi.generateObject = [gen]() -> CGObjectInstance * + { + auto obj = new CGArtifact(); + obj->ID = Obj::RANDOM_MAJOR_ART; + obj->subID = 0; + auto a = new CArtifactInstance(); + gen->map->addNewArtifactInstance(a); + obj->storedArtifact = a; + return obj; + }; + oi.value = 10000; + oi.probability = 150; + possibleObjects.push_back (oi); + + oi.generateObject = [gen]() -> CGObjectInstance * + { + auto obj = new CGArtifact(); + obj->ID = Obj::RANDOM_RELIC_ART; + obj->subID = 0; + auto a = new CArtifactInstance(); + gen->map->addNewArtifactInstance(a); + obj->storedArtifact = a; + return obj; + }; + oi.value = 20000; + oi.probability = 150; + possibleObjects.push_back (oi); + + static const int scrollValues[] = {500, 2000, 3000, 4000, 5000}; + + for (int i = 0; i < 5; i++) + { + oi.generateObject = [i, gen]() -> CGObjectInstance * + { + auto obj = new CGArtifact(); + obj->ID = Obj::SPELL_SCROLL; + obj->subID = 0; + std::vector out; + + //TODO: unify with cb->getAllowedSpells? + for (ui32 i = 0; i < gen->map->allowedSpell.size(); i++) //spellh size appears to be greater (?) + { + const CSpell *spell = SpellID(i).toSpell(); + if (gen->map->allowedSpell[spell->id] && spell->level == i+1) + { + out.push_back(spell->id); + } + } + auto a = CArtifactInstance::createScroll(RandomGeneratorUtil::nextItem(out, gen->rand)->toSpell()); + gen->map->addNewArtifactInstance(a); + obj->storedArtifact = a; + return obj; + }; + oi.value = scrollValues[i]; + oi.probability = 30; + possibleObjects.push_back (oi); + } + + //non-removable object for test + //oi.generateObject = [gen]() -> CGObjectInstance * + //{ + // auto obj = new CGMagicWell(); + // obj->ID = Obj::MAGIC_WELL; + // obj->subID = 0; + // return obj; + //}; + //oi.value = 250; + //oi.probability = 100; + //possibleObjects.push_back (oi); + + //oi.generateObject = [gen]() -> CGObjectInstance * + //{ + // auto obj = new CGObelisk(); + // obj->ID = Obj::OBELISK; + // obj->subID = 0; + // return obj; + //}; + //oi.value = 3500; + //oi.probability = 200; + //possibleObjects.push_back (oi); + + //oi.generateObject = [gen]() -> CGObjectInstance * + //{ + // auto obj = new CBank(); + // obj->ID = Obj::CREATURE_BANK; + // obj->subID = 5; //naga bank + // return obj; + //}; + //oi.value = 3000; + //oi.probability = 100; + //possibleObjects.push_back (oi); + +} diff --git a/lib/rmg/CRmgTemplateZone.h b/lib/rmg/CRmgTemplateZone.h index 1c5970de8..2ebd79589 100644 --- a/lib/rmg/CRmgTemplateZone.h +++ b/lib/rmg/CRmgTemplateZone.h @@ -15,6 +15,7 @@ #include "CMapGenerator.h" #include "float3.h" #include "../int3.h" +#include "../ResourceSet.h" //for TResource (?) class CMapGenerator; class CTileInfo; @@ -53,6 +54,22 @@ private: ETerrainType terrain; }; +class DLL_LINKAGE CTreasureInfo +{ +public: + ui32 min; + ui32 max; + ui16 density; + ui16 threshold; //how much must RNG roll to pick that zone +}; + +struct DLL_LINKAGE ObjectInfo +{ + ui32 value; + ui16 probability; + std::function generateObject; +}; + /// The CRmgTemplateZone describes a zone in a template. class DLL_LINKAGE CRmgTemplateZone { @@ -99,10 +116,9 @@ public: const std::set & getTerrainTypes() const; /// Default: all void setTerrainTypes(const std::set & value); std::set getDefaultTerrainTypes() const; - boost::optional getTerrainTypeLikeZone() const; - void setTerrainTypeLikeZone(boost::optional value); - boost::optional getTownTypeLikeZone() const; - void setTownTypeLikeZone(boost::optional value); + void setMinesAmount (TResource res, ui16 amount); + std::map getMinesInfo() const; + void setMonsterStrength (EMonsterStrength::EMonsterStrength val); float3 getCenter() const; void setCenter(const float3 &f); @@ -113,13 +129,28 @@ public: std::set getTileInfo () const; void addRequiredObject(CGObjectInstance * obj, si32 guardStrength=0); - void addMonster(CMapGenerator* gen, int3 &pos, si32 strength); - bool fill(CMapGenerator* gen); + bool addMonster(CMapGenerator* gen, int3 &pos, si32 strength); + bool createTreasurePile (CMapGenerator* gen, int3 &pos); + bool fill (CMapGenerator* gen); + bool placeMines (CMapGenerator* gen); + void initTownType (CMapGenerator* gen); + void initTerrainType (CMapGenerator* gen); void createBorder(CMapGenerator* gen); - bool crunchPath (CMapGenerator* gen, const int3 &src, const int3 &dst, TRmgTemplateZoneId zone); + void fractalize(CMapGenerator* gen); + bool createRequiredObjects(CMapGenerator* gen); + void createTreasures(CMapGenerator* gen); + void createObstacles(CMapGenerator* gen); + bool crunchPath (CMapGenerator* gen, const int3 &src, const int3 &dst, TRmgTemplateZoneId zone, std::set* clearedTiles = nullptr); + std::vector getAccessibleOffsets (CMapGenerator* gen, CGObjectInstance* object); + void setTotalDensity (ui16 val); + ui16 getTotalDensity () const; void addConnection(TRmgTemplateZoneId otherZone); std::vector getConnections() const; + void addTreasureInfo(CTreasureInfo & info); + std::vector getTreasureInfo(); + + ObjectInfo getRandomObject (CMapGenerator* gen, ui32 value); private: //template info @@ -132,10 +163,17 @@ private: std::set townTypes; bool matchTerrainToTown; std::set terrainTypes; - boost::optional terrainTypeLikeZone, townTypeLikeZone; + std::map mines; //obligatory mines to spawn in this zone + + si32 townType; + ETerrainType terrainType; + + EMonsterStrength::EMonsterStrength zoneMonsterStrength; + ui16 totalDensity; + std::vector treasureInfo; + std::vector possibleObjects; //content info - std::vector shape; //TODO: remove std::vector> requiredObjects; std::vector objects; @@ -147,7 +185,9 @@ private: std::map alreadyConnected; //TODO: allow multiple connections between two zones? bool pointIsIn(int x, int y); + void addAllPossibleObjects (CMapGenerator* gen); //add objects, including zone-specific, to possibleObjects bool findPlaceForObject(CMapGenerator* gen, CGObjectInstance* obj, si32 min_dist, int3 &pos); + bool findPlaceForTreasurePile(CMapGenerator* gen, si32 min_dist, int3 &pos); void checkAndPlaceObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos); void placeObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos); bool guardObject(CMapGenerator* gen, CGObjectInstance* object, si32 str); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 19e91c912..98255ea42 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -8,9 +8,7 @@ #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CBuildingHandler.h" -#include "../lib/CDefObjInfoHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" @@ -23,7 +21,7 @@ #include "../lib/mapping/CMap.h" #include "../lib/VCMIDirs.h" #include "../lib/ScopeGuard.h" -#include "../client/CSoundBase.h" +#include "../lib/CSoundBase.h" #include "CGameHandler.h" #include "CVCMIServer.h" #include "../lib/CCreatureSet.h" diff --git a/server/CQuery.cpp b/server/CQuery.cpp index 0caed13b1..e7ecb0355 100644 --- a/server/CQuery.cpp +++ b/server/CQuery.cpp @@ -293,6 +293,11 @@ bool CGarrisonDialogQuery::blocksPack(const CPack *pack) const return !vstd::contains(ourIds, dismiss->id); } + if (auto dismiss = dynamic_cast(pack)) + { + return !vstd::contains(ourIds, dismiss->heroID); + } + return CDialogQuery::blocksPack(pack); } diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 0006ad594..31c558746 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -8,7 +8,6 @@ #include "../lib/Connection.h" #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" -#include "../lib/CDefObjInfoHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" @@ -26,7 +25,6 @@ #include "../lib/VCMIDirs.h" #include "CGameHandler.h" #include "../lib/mapping/CMapInfo.h" -#include "../lib/CObjectHandler.h" #include "../lib/GameConstants.h" #include "../lib/logging/CBasicLogConfigurator.h" #include "../lib/CConfigHandler.h" @@ -109,7 +107,7 @@ void CPregameServer::handleConnection(CConnection *cpc) while(state == RUNNING) boost::this_thread::sleep(boost::posix_time::milliseconds(50)); } } - } + } catch (const std::exception& e) { boost::unique_lock queueLock(mx); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 12c76ca94..597479fb1 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -2,7 +2,6 @@ #include "../lib/NetPacks.h" #include "CGameHandler.h" -#include "../lib/CObjectHandler.h" #include "../lib/IGameCallback.h" #include "../lib/mapping/CMap.h" #include "../lib/CGameState.h" diff --git a/server/StdInc.h b/server/StdInc.h index 747622649..7418241e0 100644 --- a/server/StdInc.h +++ b/server/StdInc.h @@ -4,10 +4,7 @@ #include #include //no i/o just types -//#include -//#include #include #include #include #include -//#include