From 5d2e47fabac6be04f7ab95811bd9de35bb4591d1 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 27 Nov 2015 11:04:01 +0300 Subject: [PATCH 01/27] CGHeroInstance::Patrol: add initialPos --- lib/mapObjects/CGHeroInstance.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 748f84685..3e995682d 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -76,12 +76,13 @@ public: struct DLL_LINKAGE Patrol { - Patrol(){patrolling=false;patrolRadious=-1;}; + Patrol(){patrolling=false;initialPos=int3();patrolRadious=-1;}; bool patrolling; + int3 initialPos; ui32 patrolRadious; template void serialize(Handler &h, const int version) { - h & patrolling & patrolRadious; + h & patrolling & initialPos & patrolRadious; } } patrol; From d6a142ca3d34a49a954616cb76c9048c0243f917 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 27 Nov 2015 11:34:03 +0300 Subject: [PATCH 02/27] CMapLoaderH3M: support for patrol initialPos in readHero --- lib/mapping/MapFormatH3M.cpp | 5 +++-- lib/mapping/MapFormatH3M.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 1831e373a..869681360 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1060,7 +1060,7 @@ void CMapLoaderH3M::readObjects() case Obj::RANDOM_HERO: case Obj::PRISON: { - nobj = readHero(idToBeGiven); + nobj = readHero(idToBeGiven, objPos); break; } case Obj::MONSTER: //Monster @@ -1549,7 +1549,7 @@ void CMapLoaderH3M::readCreatureSet(CCreatureSet * out, int number) out->validTypes(true); } -CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven) +CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const int3 & initialPos) { auto nhi = new CGHeroInstance(); @@ -1658,6 +1658,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven) else { nhi->patrol.patrolling = true; + nhi->patrol.initialPos = initialPos; } if(map->version > EMapFormat::ROE) diff --git a/lib/mapping/MapFormatH3M.h b/lib/mapping/MapFormatH3M.h index b37fd5640..5d306bc06 100644 --- a/lib/mapping/MapFormatH3M.h +++ b/lib/mapping/MapFormatH3M.h @@ -172,7 +172,7 @@ private: * @param idToBeGiven the object id which should be set for the hero * @return a object instance */ - CGObjectInstance * readHero(ObjectInstanceID idToBeGiven); + CGObjectInstance * readHero(ObjectInstanceID idToBeGiven, const int3 & initialPos); /** * Reads a seer hut. From f6de3f94ca6a9a593fc11d4b6957133a85012a5f Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 28 Nov 2015 02:41:30 +0300 Subject: [PATCH 03/27] Teleports: use TTeleportExitsList typedef for exits list There is several ideas that teleportation code have to be shared between object/spells and this way we can avoid changing showTeleportDialog declaration every time. --- AI/EmptyAI/CEmptyAI.cpp | 2 +- AI/EmptyAI/CEmptyAI.h | 2 +- AI/VCAI/VCAI.cpp | 2 +- AI/VCAI/VCAI.h | 2 +- lib/CGameInterface.h | 4 +++- lib/NetPacks.h | 2 +- lib/mapObjects/MiscObjects.h | 2 ++ 7 files changed, 10 insertions(+), 6 deletions(-) diff --git a/AI/EmptyAI/CEmptyAI.cpp b/AI/EmptyAI/CEmptyAI.cpp index f46bc1e6e..58d8fa946 100644 --- a/AI/EmptyAI/CEmptyAI.cpp +++ b/AI/EmptyAI/CEmptyAI.cpp @@ -30,7 +30,7 @@ void CEmptyAI::showBlockingDialog(const std::string &text, const std::vectorselectionMade(0, askID); } -void CEmptyAI::showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) +void CEmptyAI::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) { cb->selectionMade(0, askID); } diff --git a/AI/EmptyAI/CEmptyAI.h b/AI/EmptyAI/CEmptyAI.h index eb5e43a32..262511a5d 100644 --- a/AI/EmptyAI/CEmptyAI.h +++ b/AI/EmptyAI/CEmptyAI.h @@ -15,7 +15,7 @@ public: void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector &skills, QueryID queryID) override; void commanderGotLevel (const CCommanderInstance * commander, std::vector skills, QueryID queryID) override; void showBlockingDialog(const std::string &text, const std::vector &components, QueryID askID, const int soundID, bool selection, bool cancel) override; - void showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) override; + void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; }; diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index a93695e94..18a1801ec 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -614,7 +614,7 @@ void VCAI::showBlockingDialog(const std::string &text, const std::vector exits, bool impassable, QueryID askID) +void VCAI::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) { LOG_TRACE_PARAMS(logAi, "askID '%i', exits '%s'", askID % exits); NET_EVENT_HANDLER; diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 23967df64..15fd7c7d4 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -202,7 +202,7 @@ public: virtual void commanderGotLevel (const CCommanderInstance * commander, std::vector skills, QueryID queryID) override; //TODO virtual void showBlockingDialog(const std::string &text, const std::vector &components, QueryID askID, const int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID. virtual void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done - virtual void showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) override; + virtual void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; virtual void saveGame(COSer & h, const int version) override; //saving virtual void loadGame(CISer & h, const int version) override; //loading virtual void finish() override; diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index c35d52985..b120d95d7 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -7,6 +7,8 @@ #include "spells/ViewSpellInt.h" +#include "mapObjects/MiscObjects.h" + /* * CGameInterface.h, part of VCMI engine * @@ -94,7 +96,7 @@ public: // all stacks operations between these objects become allowed, interface has to call onEnd when done virtual void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) = 0; - virtual void showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) = 0; + virtual void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) = 0; virtual void finish(){}; //if for some reason we want to end virtual void showWorldViewEx(const std::vector & objectPositions){}; diff --git a/lib/NetPacks.h b/lib/NetPacks.h index cdbe3ebe9..5417438f1 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1221,7 +1221,7 @@ struct TeleportDialog : public Query//2006 const CGHeroInstance *hero; TeleportChannelID channel; - std::vector exits; + TTeleportExitsList exits; bool impassable; template void serialize(Handler &h, const int version) diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index 21990b688..402deb1c4 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -247,6 +247,8 @@ public: ui32 defaultResProduction(); }; +typedef std::vector TTeleportExitsList; + struct DLL_LINKAGE TeleportChannel { enum EPassability {UNKNOWN, IMPASSABLE, PASSABLE}; From 56c6785bd0ffdbadd2f7ba4885adcc3739cabf44 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 28 Nov 2015 17:58:59 +0300 Subject: [PATCH 04/27] Add backward compatability for patrol and bump save format version --- lib/Connection.h | 2 +- lib/mapObjects/CGHeroInstance.h | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/Connection.h b/lib/Connection.h index ee07bf9aa..e56d71640 100644 --- a/lib/Connection.h +++ b/lib/Connection.h @@ -27,7 +27,7 @@ #include "mapping/CCampaignHandler.h" //for CCampaignState #include "rmg/CMapGenerator.h" // for CMapGenOptions -const ui32 version = 754; +const ui32 version = 755; const ui32 minSupportedVersion = 753; class CISer; diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 3e995682d..688c83b22 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -82,7 +82,17 @@ public: ui32 patrolRadious; template void serialize(Handler &h, const int version) { - h & patrolling & initialPos & patrolRadious; + h & patrolling; + if(version >= 755) + { + h & initialPos; + } + else if(!h.saving) + { + patrolling = false; + initialPos = int3(); + } + h & patrolRadious; } } patrol; From 6bb205b15b81d5f66a9a16da7be73fcefe82434e Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 28 Nov 2015 20:34:50 +0300 Subject: [PATCH 05/27] CPathfinder: patrol support using getTilesInRange For now we use getTilesInRange which isn't really correct for patrol, but good enough for first version. --- lib/CPathfinder.cpp | 47 +++++++++++++++++++++++++++++++++--- lib/CPathfinder.h | 11 +++++++++ lib/mapping/MapFormatH3M.cpp | 2 +- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 8be71f212..8046f461d 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -37,7 +37,7 @@ CPathfinder::PathfinderOptions::PathfinderOptions() } CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero) - : CGameInfoCallback(_gs, boost::optional()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap) + : CGameInfoCallback(_gs, boost::optional()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap), patrolTiles({}) { assert(hero); assert(hero == getHero(hero->id)); @@ -52,6 +52,7 @@ CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstan hlp = make_unique(hero, options); + initializePatrol(); initializeGraph(); neighbourTiles.reserve(8); neighbours.reserve(16); @@ -95,8 +96,10 @@ void CPathfinder::calculatePaths() CGPathNode * initialNode = out.getNode(out.hpos, hero->boat ? ELayer::SAIL : ELayer::LAND); initialNode->turns = 0; initialNode->moveRemains = hero->movement; - pq.push(initialNode); + if(isHeroPatrolLocked()) + return; + pq.push(initialNode); while(!pq.empty()) { cp = pq.top(); @@ -119,6 +122,9 @@ void CPathfinder::calculatePaths() addNeighbours(); for(auto & neighbour : neighbours) { + if(!isPatrolMovementAllowed(neighbour)) + continue; + dt = &gs->map->getTile(neighbour); dtObj = dt->topVisitableObj(); for(ELayer i = ELayer::LAND; i <= ELayer::AIR; i.advance(1)) @@ -215,7 +221,9 @@ void CPathfinder::addNeighbours() void CPathfinder::addTeleportExits() { neighbours.clear(); - if(!isSourceVisitableObj()) + /// For now we disable teleports usage for patrol movement + /// VCAI not aware about patrol and may stuck while attempt to use teleport + if(!isSourceVisitableObj() || patrolState == PATROL_RADIUS) return; const CGTeleport * objTeleport = dynamic_cast(ctObj); @@ -256,6 +264,22 @@ void CPathfinder::addTeleportExits() } } +bool CPathfinder::isHeroPatrolLocked() const +{ + return patrolState == PATROL_LOCKED; +} + +bool CPathfinder::isPatrolMovementAllowed(const int3 & dst) const +{ + if(patrolState == PATROL_RADIUS) + { + if(!vstd::contains(patrolTiles, dst)) + return false; + } + + return true; +} + bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const { /// No layer transition allowed when previous node action is BATTLE @@ -564,6 +588,23 @@ bool CPathfinder::isDestinationGuardian() const return gs->guardingCreaturePosition(cp->coord) == dp->coord; } +void CPathfinder::initializePatrol() +{ + auto state = PATROL_NONE; + if(hero->patrol.patrolling && !getPlayer(hero->tempOwner)->human) + { + if(hero->patrol.patrolRadious) + { + state = PATROL_RADIUS; + gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadious); + } + else + state = PATROL_LOCKED; + } + + patrolState = state; +} + void CPathfinder::initializeGraph() { auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo) diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 29c5bd35e..e3e533864 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -159,6 +159,13 @@ private: const std::vector > > &FoW; unique_ptr hlp; + enum EPatrolState { + PATROL_NONE = 0, + PATROL_LOCKED = 1, + PATROL_RADIUS + } patrolState; + std::unordered_set patrolTiles; + struct NodeComparer { bool operator()(const CGPathNode * lhs, const CGPathNode * rhs) const @@ -185,6 +192,9 @@ private: void addNeighbours(); void addTeleportExits(); + bool isHeroPatrolLocked() const; + bool isPatrolMovementAllowed(const int3 & dst) const; + bool isLayerTransitionPossible(const ELayer dstLayer) const; bool isLayerTransitionPossible() const; bool isMovementToDestPossible() const; @@ -198,6 +208,7 @@ private: bool isDestinationGuarded(const bool ignoreAccessibility = true) const; bool isDestinationGuardian() const; + void initializePatrol(); void initializeGraph(); CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 869681360..859abc110 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1658,7 +1658,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const i else { nhi->patrol.patrolling = true; - nhi->patrol.initialPos = initialPos; + nhi->patrol.initialPos = CGHeroInstance::convertPosition(initialPos, false); } if(map->version > EMapFormat::ROE) From 6704ea0cff605fc29feb43fccf4f49857a0589e5 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 30 Nov 2015 17:51:28 +0300 Subject: [PATCH 06/27] Rumors: rename getTavernGossip to getTavernRumor for consistency Original game used "Rumors" and not "Gossips" and we already using rumor in CMap. --- client/windows/GUIClasses.cpp | 4 +++- lib/CGameInfoCallback.cpp | 2 +- lib/CGameInfoCallback.h | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 756027ab6..3a52a6262 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -705,7 +705,9 @@ CTavernWindow::CTavernWindow(const CGObjectInstance *TavernObj): new CLabel(200, 35, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[37]); new CLabel(320, 328, FONT_SMALL, CENTER, Colors::WHITE, "2500"); - new CTextBox(LOCPLINT->cb->getTavernGossip(tavernObj), Rect(32, 190, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE); + + auto rumorText = boost::str(boost::format(CGI->generaltexth->allTexts[216]) % LOCPLINT->cb->getTavernRumor(tavernObj)); + new CTextBox(rumorText, Rect(32, 190, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE); new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); cancel = new CButton(Point(310, 428), "ICANCEL.DEF", CButton::tooltip(CGI->generaltexth->tavernInfo[7]), std::bind(&CTavernWindow::close, this), SDLK_ESCAPE); diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 56401514b..2beb8e384 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -564,7 +564,7 @@ EPlayerStatus::EStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bo return ps->status; } -std::string CGameInfoCallback::getTavernGossip(const CGObjectInstance * townOrTavern) const +std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTavern) const { return "GOSSIP TEST"; } diff --git a/lib/CGameInfoCallback.h b/lib/CGameInfoCallback.h index 7899acb10..20c94db30 100644 --- a/lib/CGameInfoCallback.h +++ b/lib/CGameInfoCallback.h @@ -99,7 +99,7 @@ public: int howManyTowns(PlayerColor Player) const; const CGTownInstance * getTownInfo(int val, bool mode)const; //mode = 0 -> val = player town serial; mode = 1 -> val = object id (serial) std::vector getAvailableHeroes(const CGObjectInstance * townOrTavern) const; //heroes that can be recruited - std::string getTavernGossip(const CGObjectInstance * townOrTavern) const; + std::string getTavernRumor(const CGObjectInstance * townOrTavern) const; EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const; const CTown *getNativeTown(PlayerColor color) const; From 879eaa47f865fd30c351c2f3b26036840e77cf45 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 30 Nov 2015 20:52:15 +0300 Subject: [PATCH 07/27] Rumors: client-side proof of concept implementation Actually information about this week rumor should be stored in gamestate and updated weekly --- lib/CGameInfoCallback.cpp | 66 ++++++++++++++++++++++++++++++++++++- lib/CGeneralTextHandler.cpp | 1 + lib/CGeneralTextHandler.h | 1 + 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 2beb8e384..cb534fb1f 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -12,6 +12,7 @@ #include "CGameInfoCallback.h" #include "CGameState.h" // PlayerState +#include "CGeneralTextHandler.h" #include "mapObjects/CObjectHandler.h" // for CGObjectInstance #include "StartInfo.h" // for StartInfo #include "BattleState.h" // for BattleInfo @@ -566,7 +567,70 @@ EPlayerStatus::EStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bo std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTavern) const { - return "GOSSIP TEST"; + std::string text = ""; + auto & rand = gs->getRandomGenerator(); + + static std::vector rumorTypes = {0, 1, 2, 2}; + auto & rumorType = *RandomGeneratorUtil::nextItem(rumorTypes, rand); + switch(rumorType) + { + case 0: + { + SThievesGuildInfo tgi; + gs->obtainPlayersStats(tgi, 20); + static std::vector statRumorTypes = {208, 209, 210};// 211, 212}; + std::vector players = {}; + auto statRumorType = *RandomGeneratorUtil::nextItem(statRumorTypes, rand); + switch(statRumorType) + { + case 208: + players = tgi.obelisks[0]; + break; + + case 209: + players = tgi.artifacts[0]; + break; + + case 210: + players = tgi.army[0]; + break; + + case 211: + /// TODO: not implemented in obtainPlayersStats + players = tgi.income[0]; + break; + + case 212: + /// TODO: Check that ultimate artifact (grail) found + break; + } + auto & playerId = *RandomGeneratorUtil::nextItem(players, rand); + std::string playerName = VLC->generaltexth->colors[playerId.getNum()]; + text = boost::str(boost::format(VLC->generaltexth->allTexts[statRumorType]) % playerName); + + break; + } + case 1: + if(gs->map->rumors.size()) + { + auto & mapRumor = *RandomGeneratorUtil::nextItem(gs->map->rumors, rand); + text = mapRumor.text; + break; + } + + /// don't break - if map don't have rumors we show predefined instead + + case 2: + do + { + text = *RandomGeneratorUtil::nextItem(VLC->generaltexth->tavernRumors, rand); + } + while(!text.length()); + + break; + } + + return text; } PlayerRelations::PlayerRelations CGameInfoCallback::getPlayerRelations( PlayerColor color1, PlayerColor color2 ) const diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 2481fb669..03465d888 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -327,6 +327,7 @@ CGeneralTextHandler::CGeneralTextHandler() readToVector("DATA/PRISKILL.TXT", primarySkillNames); readToVector("DATA/JKTEXT.TXT", jktexts); readToVector("DATA/TVRNINFO.TXT", tavernInfo); + readToVector("DATA/RANDTVRN.TXT", tavernRumors); readToVector("DATA/TURNDUR.TXT", turnDurations); readToVector("DATA/HEROSCRN.TXT", heroscrn); readToVector("DATA/TENTCOLR.TXT", tentColors); diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index a80531cfd..bc04c3afc 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -109,6 +109,7 @@ public: //towns std::vector tcommands, hcommands, fcommands; //texts for town screen, town hall screen and fort screen std::vector tavernInfo; + std::vector tavernRumors; std::vector > zelp; std::vector lossCondtions; From 98582d628c9095e92f2c927a843b742b24b00c43 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 1 Dec 2015 00:44:58 +0300 Subject: [PATCH 08/27] Rumors: move code into CGameState and add backward compatability --- lib/CGameInfoCallback.cpp | 65 +++++++----------------------------- lib/CGameState.cpp | 69 +++++++++++++++++++++++++++++++++++++++ lib/CGameState.h | 27 +++++++++++++++ lib/Connection.h | 2 +- lib/NetPacksLib.cpp | 3 ++ 5 files changed, 111 insertions(+), 55 deletions(-) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index cb534fb1f..c5aa3399c 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -568,65 +568,22 @@ EPlayerStatus::EStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bo std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTavern) const { std::string text = ""; - auto & rand = gs->getRandomGenerator(); + if(gs->rumor.type == RumorState::RUMOR_NONE) // (version < 755 backward compatability + return text; - static std::vector rumorTypes = {0, 1, 2, 2}; - auto & rumorType = *RandomGeneratorUtil::nextItem(rumorTypes, rand); - switch(rumorType) + auto rumor = gs->rumor.last[gs->rumor.type]; + switch(gs->rumor.type) { - case 0: - { - SThievesGuildInfo tgi; - gs->obtainPlayersStats(tgi, 20); - static std::vector statRumorTypes = {208, 209, 210};// 211, 212}; - std::vector players = {}; - auto statRumorType = *RandomGeneratorUtil::nextItem(statRumorTypes, rand); - switch(statRumorType) - { - case 208: - players = tgi.obelisks[0]; - break; - - case 209: - players = tgi.artifacts[0]; - break; - - case 210: - players = tgi.army[0]; - break; - - case 211: - /// TODO: not implemented in obtainPlayersStats - players = tgi.income[0]; - break; - - case 212: - /// TODO: Check that ultimate artifact (grail) found - break; - } - auto & playerId = *RandomGeneratorUtil::nextItem(players, rand); - std::string playerName = VLC->generaltexth->colors[playerId.getNum()]; - text = boost::str(boost::format(VLC->generaltexth->allTexts[statRumorType]) % playerName); - + case RumorState::RUMOR_STATS: + text = boost::str(boost::format(VLC->generaltexth->allTexts[rumor.first]) % VLC->generaltexth->colors[rumor.second]); break; - } - case 1: - if(gs->map->rumors.size()) - { - auto & mapRumor = *RandomGeneratorUtil::nextItem(gs->map->rumors, rand); - text = mapRumor.text; - break; - } - /// don't break - if map don't have rumors we show predefined instead - - case 2: - do - { - text = *RandomGeneratorUtil::nextItem(VLC->generaltexth->tavernRumors, rand); - } - while(!text.length()); + case RumorState::RUMOR_MAP: + text = gs->map->rumors[rumor.first].text; + break; + case RumorState::RUMOR_RAND: + text = VLC->generaltexth->tavernRumors[rumor.first]; break; } diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 411628370..e50caeb6e 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2133,6 +2133,75 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const return gs->map->guardingCreaturePositions[pos.x][pos.y][pos.z]; } +void CGameState::updateRumor() +{ + static std::vector rumorTypes = {RumorState::RUMOR_MAP, RumorState::RUMOR_STATS, RumorState::RUMOR_RAND, RumorState::RUMOR_RAND}; + static std::vector statsRumorTypes = {208, 209, 210};// 211, 212}; + + int rumorId = -1; + int rumorPlayer = PlayerColor::CANNOT_DETERMINE.getNum(); + auto & rand = gs->getRandomGenerator(); + rumor.type = *RandomGeneratorUtil::nextItem(rumorTypes, rand); + if(!gs->map->rumors.size() && rumor.type == RumorState::RUMOR_MAP) + rumor.type = RumorState::RUMOR_RAND; + + switch(rumor.type) + { + case RumorState::RUMOR_STATS: + { + SThievesGuildInfo tgi; + gs->obtainPlayersStats(tgi, 20); + std::vector players = {}; + rumorId = *RandomGeneratorUtil::nextItem(statsRumorTypes, rand); + switch(rumorId) + { + case 208: + players = tgi.obelisks[0]; + break; + + case 209: + players = tgi.artifacts[0]; + break; + + case 210: + players = tgi.army[0]; + break; + + case 211: + /// TODO: not implemented in obtainPlayersStats + players = tgi.income[0]; + break; + + case 212: + /// TODO: Check that ultimate artifact (grail) found + break; + } + rumorPlayer = RandomGeneratorUtil::nextItem(players, rand)->getNum(); + + break; + } + case RumorState::RUMOR_MAP: + rumorId = rand.nextInt(gs->map->rumors.size() - 1); + + break; + + case RumorState::RUMOR_RAND: + do + { + rumorId = rand.nextInt(VLC->generaltexth->tavernRumors.size() - 1); + } + while(!VLC->generaltexth->tavernRumors[rumorId].length()); + + break; + } + + if(vstd::contains(rumor.last, rumor.type)) + { + rumor.last.erase(rumor.type); + } + rumor.last[rumor.type] = std::make_pair(rumorId, rumorPlayer); +} + bool CGameState::isVisible(int3 pos, PlayerColor player) { if(player == PlayerColor::NEUTRAL) diff --git a/lib/CGameState.h b/lib/CGameState.h index c60a9aa34..61ad827da 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -208,6 +208,24 @@ public: }; +struct DLL_LINKAGE RumorState +{ + enum ERumorType : ui8 + { + RUMOR_NONE = 0, RUMOR_RAND, RUMOR_STATS, RUMOR_MAP + }; + + ERumorType type; + std::map> last; + + RumorState(){type = RUMOR_NONE; last = {};}; + + template void serialize(Handler &h, const int version) + { + h & type & last; + } +}; + struct UpgradeInfo { CreatureID oldID; //creature to be upgraded @@ -311,6 +329,7 @@ public: std::map players; std::map teams; CBonusSystemNode globalEffects; + RumorState rumor; boost::shared_mutex *mx; @@ -324,6 +343,7 @@ public: void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists int3 guardingCreaturePosition (int3 pos) const; std::vector guardingCreatures (int3 pos) const; + void updateRumor(); // ----- victory, loss condition checks ----- @@ -347,6 +367,13 @@ public: template void serialize(Handler &h, const int version) { h & scenarioOps & initialOpts & currentPlayer & day & map & players & teams & hpool & globalEffects & rand; + if(version >= 755) + { + h & rumor; + } + else + rumor = RumorState(); + BONUS_TREE_DESERIALIZATION_FIX } diff --git a/lib/Connection.h b/lib/Connection.h index ee07bf9aa..e56d71640 100644 --- a/lib/Connection.h +++ b/lib/Connection.h @@ -27,7 +27,7 @@ #include "mapping/CCampaignHandler.h" //for CCampaignState #include "rmg/CMapGenerator.h" // for CMapGenOptions -const ui32 version = 754; +const ui32 version = 755; const ui32 minSupportedVersion = 753; class CISer; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 5483c2936..e7235da68 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -1032,6 +1032,9 @@ DLL_LINKAGE void NewTurn::applyGs( CGameState *gs ) for(CGTownInstance* t : gs->map->towns) t->builded = 0; + + if(gs->getDate(Date::DAY_OF_WEEK) == 1) + gs->updateRumor(); } DLL_LINKAGE void SetObjectProperty::applyGs( CGameState *gs ) From 15273ac0f7ecfb2f0d139d93c6bc03b112865a9d Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 1 Dec 2015 02:14:11 +0300 Subject: [PATCH 09/27] Rumors: avoid repeating of same rumor twice in a row Multiple rumors of same type can go in a row, but not identical rumors. --- lib/CGameInfoCallback.cpp | 2 +- lib/CGameState.cpp | 101 ++++++++++++++++++++++---------------- lib/CGameState.h | 3 +- 3 files changed, 62 insertions(+), 44 deletions(-) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index c5aa3399c..1a968ab81 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -575,7 +575,7 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav switch(gs->rumor.type) { case RumorState::RUMOR_STATS: - text = boost::str(boost::format(VLC->generaltexth->allTexts[rumor.first]) % VLC->generaltexth->colors[rumor.second]); + text = boost::str(boost::format(VLC->generaltexth->allTexts[rumor.first]) % VLC->generaltexth->capColors[rumor.second]); break; case RumorState::RUMOR_MAP: diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index e50caeb6e..97e57bafa 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2145,61 +2145,59 @@ void CGameState::updateRumor() if(!gs->map->rumors.size() && rumor.type == RumorState::RUMOR_MAP) rumor.type = RumorState::RUMOR_RAND; - switch(rumor.type) + do { - case RumorState::RUMOR_STATS: - { - SThievesGuildInfo tgi; - gs->obtainPlayersStats(tgi, 20); - std::vector players = {}; - rumorId = *RandomGeneratorUtil::nextItem(statsRumorTypes, rand); - switch(rumorId) + switch(rumor.type) { - case 208: - players = tgi.obelisks[0]; - break; + case RumorState::RUMOR_STATS: + { + SThievesGuildInfo tgi; + gs->obtainPlayersStats(tgi, 20); + std::vector players = {}; + rumorId = *RandomGeneratorUtil::nextItem(statsRumorTypes, rand); + switch(rumorId) + { + case 208: + players = tgi.obelisks[0]; + break; - case 209: - players = tgi.artifacts[0]; - break; + case 209: + players = tgi.artifacts[0]; + break; - case 210: - players = tgi.army[0]; - break; + case 210: + players = tgi.army[0]; + break; - case 211: - /// TODO: not implemented in obtainPlayersStats - players = tgi.income[0]; - break; + case 211: + /// TODO: not implemented in obtainPlayersStats + players = tgi.income[0]; + break; + + case 212: + /// TODO: Check that ultimate artifact (grail) found + break; + } + rumorPlayer = RandomGeneratorUtil::nextItem(players, rand)->getNum(); - case 212: - /// TODO: Check that ultimate artifact (grail) found break; } - rumorPlayer = RandomGeneratorUtil::nextItem(players, rand)->getNum(); + case RumorState::RUMOR_MAP: + rumorId = rand.nextInt(gs->map->rumors.size() - 1); - break; - } - case RumorState::RUMOR_MAP: - rumorId = rand.nextInt(gs->map->rumors.size() - 1); + break; - break; + case RumorState::RUMOR_RAND: + do + { + rumorId = rand.nextInt(VLC->generaltexth->tavernRumors.size() - 1); + } + while(!VLC->generaltexth->tavernRumors[rumorId].length()); - case RumorState::RUMOR_RAND: - do - { - rumorId = rand.nextInt(VLC->generaltexth->tavernRumors.size() - 1); + break; } - while(!VLC->generaltexth->tavernRumors[rumorId].length()); - - break; } - - if(vstd::contains(rumor.last, rumor.type)) - { - rumor.last.erase(rumor.type); - } - rumor.last[rumor.type] = std::make_pair(rumorId, rumorPlayer); + while(!rumor.update(rumorId, rumorPlayer)); } bool CGameState::isVisible(int3 pos, PlayerColor player) @@ -2868,6 +2866,25 @@ std::string PlayerState::nodeName() const return "Player " + (color.getNum() < VLC->generaltexth->capColors.size() ? VLC->generaltexth->capColors[color.getNum()] : boost::lexical_cast(color)); } + +bool RumorState::update(int id, int player) +{ + if(vstd::contains(last, type)) + { + if(last[type].first != id) + { + last[type].first = id; + last[type].second = player; + } + else + return false; + } + else + last[type] = std::make_pair(id, player); + + return true; +} + InfoAboutArmy::InfoAboutArmy(): owner(PlayerColor::NEUTRAL) {} diff --git a/lib/CGameState.h b/lib/CGameState.h index 61ad827da..90e8c9962 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -219,6 +219,7 @@ struct DLL_LINKAGE RumorState std::map> last; RumorState(){type = RUMOR_NONE; last = {};}; + bool update(int id, int player); template void serialize(Handler &h, const int version) { @@ -371,7 +372,7 @@ public: { h & rumor; } - else + else if(!h.saving) rumor = RumorState(); BONUS_TREE_DESERIALIZATION_FIX From c5485ceede72fb527162f101f28845ad958c717c Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 1 Dec 2015 02:23:43 +0300 Subject: [PATCH 10/27] Rumors: update changelog --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index 698c2b1e2..8308ec4e3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,7 @@ GENERAL: * New artifacts supported - Angel Wings - Boots of Levitation +* Implemented rumors in tavern window ADVETURE AI: * Fixed AI trying to go through underground rock From ffcc5ccdd27570a37b00c5d0a1dff27bac9358b7 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 1 Dec 2015 04:57:52 +0300 Subject: [PATCH 11/27] Rumors: implement support for grail terrain rumor --- lib/CGameInfoCallback.cpp | 11 ++++++++--- lib/CGameState.cpp | 27 ++++++++++++++------------- lib/CGameState.h | 2 +- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 1a968ab81..d51e9ac65 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -567,7 +567,7 @@ EPlayerStatus::EStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bo std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTavern) const { - std::string text = ""; + std::string text = "", extraText = ""; if(gs->rumor.type == RumorState::RUMOR_NONE) // (version < 755 backward compatability return text; @@ -575,9 +575,14 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav switch(gs->rumor.type) { case RumorState::RUMOR_STATS: - text = boost::str(boost::format(VLC->generaltexth->allTexts[rumor.first]) % VLC->generaltexth->capColors[rumor.second]); - break; + if(rumor.first == 212) + extraText = VLC->generaltexth->arraytxt[158 + rumor.second]; + else + extraText = VLC->generaltexth->capColors[rumor.second]; + text = boost::str(boost::format(VLC->generaltexth->allTexts[rumor.first]) % extraText); + + break; case RumorState::RUMOR_MAP: text = gs->map->rumors[rumor.first].text; break; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 97e57bafa..e48133a3a 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2136,10 +2136,9 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const void CGameState::updateRumor() { static std::vector rumorTypes = {RumorState::RUMOR_MAP, RumorState::RUMOR_STATS, RumorState::RUMOR_RAND, RumorState::RUMOR_RAND}; - static std::vector statsRumorTypes = {208, 209, 210};// 211, 212}; + static std::vector statsRumorTypes = {208, 209, 210, 212};// 211}; - int rumorId = -1; - int rumorPlayer = PlayerColor::CANNOT_DETERMINE.getNum(); + int rumorId = -1, rumorExtra = -1; auto & rand = gs->getRandomGenerator(); rumor.type = *RandomGeneratorUtil::nextItem(rumorTypes, rand); if(!gs->map->rumors.size() && rumor.type == RumorState::RUMOR_MAP) @@ -2153,8 +2152,14 @@ void CGameState::updateRumor() { SThievesGuildInfo tgi; gs->obtainPlayersStats(tgi, 20); - std::vector players = {}; rumorId = *RandomGeneratorUtil::nextItem(statsRumorTypes, rand); + if(rumorId == 212) + { + rumorExtra = getTile(map->grailPos)->terType; + break; + } + + std::vector players = {}; switch(rumorId) { case 208: @@ -2173,12 +2178,8 @@ void CGameState::updateRumor() /// TODO: not implemented in obtainPlayersStats players = tgi.income[0]; break; - - case 212: - /// TODO: Check that ultimate artifact (grail) found - break; } - rumorPlayer = RandomGeneratorUtil::nextItem(players, rand)->getNum(); + rumorExtra = RandomGeneratorUtil::nextItem(players, rand)->getNum(); break; } @@ -2197,7 +2198,7 @@ void CGameState::updateRumor() break; } } - while(!rumor.update(rumorId, rumorPlayer)); + while(!rumor.update(rumorId, rumorExtra)); } bool CGameState::isVisible(int3 pos, PlayerColor player) @@ -2867,20 +2868,20 @@ std::string PlayerState::nodeName() const } -bool RumorState::update(int id, int player) +bool RumorState::update(int id, int extra) { if(vstd::contains(last, type)) { if(last[type].first != id) { last[type].first = id; - last[type].second = player; + last[type].second = extra; } else return false; } else - last[type] = std::make_pair(id, player); + last[type] = std::make_pair(id, extra); return true; } diff --git a/lib/CGameState.h b/lib/CGameState.h index 90e8c9962..cab3f572b 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -219,7 +219,7 @@ struct DLL_LINKAGE RumorState std::map> last; RumorState(){type = RUMOR_NONE; last = {};}; - bool update(int id, int player); + bool update(int id, int extra); template void serialize(Handler &h, const int version) { From a62ee65d72a655d18c7f2d07c2a472d4438a6db4 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 1 Dec 2015 06:28:08 +0300 Subject: [PATCH 12/27] Rumors: implement income checking via statsHLP::getIncome This also fix income ranking in Thieves' Guild --- lib/CGameState.cpp | 57 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index e48133a3a..846ee32c7 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2136,7 +2136,7 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const void CGameState::updateRumor() { static std::vector rumorTypes = {RumorState::RUMOR_MAP, RumorState::RUMOR_STATS, RumorState::RUMOR_RAND, RumorState::RUMOR_RAND}; - static std::vector statsRumorTypes = {208, 209, 210, 212};// 211}; + static std::vector statsRumorTypes = {208, 209, 210, 211, 212}; int rumorId = -1, rumorExtra = -1; auto & rand = gs->getRandomGenerator(); @@ -2175,7 +2175,6 @@ void CGameState::updateRumor() break; case 211: - /// TODO: not implemented in obtainPlayersStats players = tgi.income[0]; break; } @@ -2522,6 +2521,58 @@ struct statsHLP } return str; } + + // get total gold income + static int getIncome(const PlayerState * ps) + { + int totalIncome = 0; + const CGObjectInstance * heroOrTown = nullptr; + + //Heroes can produce gold as well - skill, specialty or arts + for(auto & h : ps->heroes) + { + totalIncome += h->valOfBonuses(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::ESTATES)); + totalIncome += h->valOfBonuses(Selector::typeSubtype(Bonus::GENERATE_RESOURCE, Res::GOLD)); + + if(!heroOrTown) + heroOrTown = h; + } + + //Add town income of all towns + for(auto & t : ps->towns) + { + totalIncome += t->dailyIncome()[Res::GOLD]; + + if(!heroOrTown) + heroOrTown = t; + } + + /// FIXME: Dirty dirty hack + /// Stats helper need some access to gamestate. + std::vector ownedObjects; + for(const CGObjectInstance * obj : heroOrTown->cb->gameState()->map->objects) + { + if(obj && obj->tempOwner == ps->color) + ownedObjects.push_back(obj); + } + /// This is code from CPlayerSpecificInfoCallback::getMyObjects + /// I'm really need to find out about callback interface design... + + for(auto object : ownedObjects) + { + //Mines + if ( object->ID == Obj::MINE ) + { + const CGMine *mine = dynamic_cast(object); + assert(mine); + + if (mine->producedResource == Res::GOLD) + totalIncome += mine->producedQuantity; + } + } + + return totalIncome; + } }; void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) @@ -2596,7 +2647,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) } if(level >= 7) //income { - //TODO:obtainPlayersStats - income + FILL_FIELD(income, statsHLP::getIncome(&g->second)) } if(level >= 8) //best hero's stats { From b5100bee94e28d33dcf9d14835ad895b40f1d57b Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Wed, 2 Dec 2015 17:56:26 +0300 Subject: [PATCH 13/27] Teleport: rework code to support exit point selection for whirlpools --- AI/VCAI/VCAI.cpp | 20 +++++++----- AI/VCAI/VCAI.h | 9 ++++++ client/CPlayerInterface.cpp | 14 ++++++--- client/CPlayerInterface.h | 3 +- lib/Connection.h | 2 +- lib/mapObjects/MiscObjects.cpp | 57 +++++++++++++++++++++------------- lib/mapObjects/MiscObjects.h | 8 ++--- 7 files changed, 72 insertions(+), 41 deletions(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 18a1801ec..e84694703 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -96,6 +96,7 @@ VCAI::VCAI(void) LOG_TRACE(logAi); makingTurn = nullptr; destinationTeleport = ObjectInstanceID(); + destinationTeleportPos = int3(); } VCAI::~VCAI(void) @@ -616,31 +617,32 @@ void VCAI::showBlockingDialog(const std::string &text, const std::vectorpassability = TeleportChannel::IMPASSABLE; else { - if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, destinationTeleport)) - choosenExit = destinationTeleport; + auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos); + if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit)) + choosenExit = vstd::find_pos(exits, neededExit); - if(!status.channelProbing()) +/* if(!status.channelProbing()) { vstd::copy_if(exits, vstd::set_inserter(teleportChannelProbingList), [&](ObjectInstanceID id) -> bool { return !(vstd::contains(visitableObjs, cb->getObj(id)) || id == choosenExit); }); - } + }*/ } requestActionASAP([=]() { - answerQuery(askID, choosenExit.getNum()); + answerQuery(askID, choosenExit); }); } @@ -1863,8 +1865,10 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) auto doTeleportMovement = [&](int3 dst, ObjectInstanceID exitId) { destinationTeleport = exitId; - cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true)); + destinationTeleportPos = CGHeroInstance::convertPosition(dst, true); + cb->moveHero(*h, destinationTeleportPos); destinationTeleport = ObjectInstanceID(); + destinationTeleportPos = int3(); afterMovementCheck(); }; diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 15fd7c7d4..aa5eb521b 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -148,6 +148,7 @@ public: std::map > knownTeleportChannels; std::map knownSubterraneanGates; ObjectInstanceID destinationTeleport; + int3 destinationTeleportPos; std::vector teleportChannelProbingList; //list of teleport channel exits that not visible and need to be (re-)explored //std::vector visitedThisWeek; //only OPWs std::map > townVisitsThisWeek; @@ -360,6 +361,14 @@ public: template void serializeInternal(Handler &h, const int version) { h & knownTeleportChannels & knownSubterraneanGates & destinationTeleport; + if(version >= 755) + { + h & destinationTeleportPos; + } + else if(!h.saving) + { + destinationTeleportPos = int3(); + } h & townVisitsThisWeek & lockedHeroes & reservedHeroesMap; //FIXME: cannot instantiate abstract class h & visitableObjs & alreadyVisited & reservedObjs; h & saving & status & battlename; diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 3b5bdf7f2..6132d8035 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -97,6 +97,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player) { logGlobal->traceStream() << "\tHuman player interface for player " << Player << " being constructed"; destinationTeleport = ObjectInstanceID(); + destinationTeleportPos = int3(); observerInDuelMode = false; howManyPeople++; GH.defActionsDef = 0; @@ -1147,14 +1148,15 @@ void CPlayerInterface::showBlockingDialog( const std::string &text, const std::v } -void CPlayerInterface::showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) +void CPlayerInterface::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) { EVENT_HANDLER_CALLED_BY_CLIENT; - ObjectInstanceID choosenExit; - if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, destinationTeleport)) - choosenExit = destinationTeleport; + int choosenExit = -1; + auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos); + if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit)) + choosenExit = vstd::find_pos(exits, neededExit); - cb->selectionMade(choosenExit.getNum(), askID); + cb->selectionMade(choosenExit, askID); } void CPlayerInterface::tileRevealed(const std::unordered_set &pos) @@ -1414,6 +1416,7 @@ void CPlayerInterface::requestRealized( PackageApplied *pa ) && stillMoveHero.get() == DURING_MOVE) { // After teleportation via CGTeleport object is finished destinationTeleport = ObjectInstanceID(); + destinationTeleportPos = int3(); stillMoveHero.setn(CONTINUE_MOVE); } } @@ -2663,6 +2666,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) { CCS->soundh->stopSound(sh); destinationTeleport = nextObject->id; + destinationTeleportPos = nextCoord; doMovement(h->pos, false); sh = CCS->soundh->playSound(CCS->soundh->horseSounds[currentTerrain], -1); continue; diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index 2e19aae30..ad9e43e85 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -90,6 +90,7 @@ class CPlayerInterface : public CGameInterface, public IUpdateable public: bool observerInDuelMode; ObjectInstanceID destinationTeleport; //contain -1 or object id if teleportation + int3 destinationTeleportPos; //minor interfaces CondSh *showingDialog; //indicates if dialog box is displayed @@ -168,7 +169,7 @@ public: void showRecruitmentDialog(const CGDwelling *dwelling, const CArmedInstance *dst, int level) override; void showShipyardDialog(const IShipyard *obj) override; //obj may be town or shipyard; void showBlockingDialog(const std::string &text, const std::vector &components, QueryID askID, int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID. - void showTeleportDialog(TeleportChannelID channel, std::vector exits, bool impassable, QueryID askID) override; + void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override; void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; void showPuzzleMap() override; void viewWorldMap() override; diff --git a/lib/Connection.h b/lib/Connection.h index ee07bf9aa..e56d71640 100644 --- a/lib/Connection.h +++ b/lib/Connection.h @@ -27,7 +27,7 @@ #include "mapping/CCampaignHandler.h" //for CCampaignState #include "rmg/CMapGenerator.h" // for CMapGenOptions -const ui32 version = 754; +const ui32 version = 755; const ui32 minSupportedVersion = 753; class CISer; diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 4f2566960..8c0db729d 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -834,7 +834,7 @@ bool CGTeleport::isTeleport(const CGObjectInstance * obj) bool CGTeleport::isConnected(const CGTeleport * src, const CGTeleport * dst) { - return src && dst && src != dst && src->isChannelExit(dst->id); + return src && dst && src->isChannelExit(dst->id); } bool CGTeleport::isConnected(const CGObjectInstance * src, const CGObjectInstance * dst) @@ -913,7 +913,13 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const if(isEntrance()) { if(cb->isTeleportChannelBidirectional(channel) && 1 < cb->getTeleportChannelExits(channel).size()) - td.exits = cb->getTeleportChannelExits(channel); + { + auto exits = cb->getTeleportChannelExits(channel); + for(auto exit : exits) + { + td.exits.push_back(std::make_pair(exit, CGHeroInstance::convertPosition(cb->getObj(exit)->visitablePos(), true))); + } + } if(cb->isTeleportChannelImpassable(channel)) { @@ -929,9 +935,9 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const cb->showTeleportDialog(&td); } -void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const +void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const { - ObjectInstanceID objId(answer); + int3 dPos; auto realExits = getAllExits(true); if(!isEntrance() // Do nothing if hero visited exit only object || (!exits.size() && !realExits.size()) // Do nothing if there no exits on this channel @@ -939,14 +945,12 @@ void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, { return; } - else if(objId == ObjectInstanceID()) - objId = getRandomExit(hero); + else if(vstd::isValidIndex(exits, answer)) + dPos = exits[answer].second; else - assert(vstd::contains(exits, objId)); // Likely cheating attempt: not random teleporter choosen, but it's not from provided list + dPos = CGHeroInstance::convertPosition(cb->getObj(getRandomExit(hero))->visitablePos(), true); - auto obj = cb->getObj(objId); - if(obj) - cb->moveHero(hero->id,CGHeroInstance::convertPosition(obj->pos,true) - getVisitableOffset(), true); + cb->moveHero(hero->id, dPos, true); } void CGMonolith::initObj() @@ -986,7 +990,10 @@ void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const td.impassable = true; } else - td.exits.push_back(getRandomExit(h)); + { + auto exit = getRandomExit(h); + td.exits.push_back(std::make_pair(exit, CGHeroInstance::convertPosition(cb->getObj(exit)->visitablePos(), true))); + } cb->showTeleportDialog(&td); } @@ -1085,29 +1092,35 @@ void CGWhirlpool::onHeroVisit( const CGHeroInstance * h ) const cb->changeStackCount(StackLocation(h, targetstack), -countToTake); } else - td.exits = getAllExits(true); + { + auto exits = getAllExits(); + for(auto exit : exits) + { + auto blockedPosList = cb->getObj(exit)->getBlockedPos(); + for(auto bPos : blockedPosList) + td.exits.push_back(std::make_pair(exit, CGHeroInstance::convertPosition(bPos, true))); + } + } cb->showTeleportDialog(&td); } -void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const +void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const { - ObjectInstanceID objId(answer); + int3 dPos; auto realExits = getAllExits(); if(!exits.size() && !realExits.size()) return; - else if(objId == ObjectInstanceID()) - objId = getRandomExit(hero); + else if(vstd::isValidIndex(exits, answer)) + dPos = exits[answer].second; else - assert(vstd::contains(exits, objId)); // Likely cheating attempt: not random teleporter choosen, but it's not from provided list - - auto obj = cb->getObj(objId); - if(obj) { + auto obj = cb->getObj(getRandomExit(hero)); std::set tiles = obj->getBlockedPos(); - auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()); - cb->moveHero(hero->id, CGHeroInstance::convertPosition(tile, true), true); + dPos = CGHeroInstance::convertPosition(*RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()), true); } + + cb->moveHero(hero->id, dPos, true); } bool CGWhirlpool::isProtected( const CGHeroInstance * h ) diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index 402deb1c4..161fe38e7 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -247,7 +247,7 @@ public: ui32 defaultResProduction(); }; -typedef std::vector TTeleportExitsList; +typedef std::vector> TTeleportExitsList; struct DLL_LINKAGE TeleportChannel { @@ -282,7 +282,7 @@ public: std::vector getAllExits(bool excludeCurrent = false) const; ObjectInstanceID getRandomExit(const CGHeroInstance * h) const; - virtual void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const = 0; + virtual void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const = 0; static bool isTeleport(const CGObjectInstance * dst); static bool isConnected(const CGTeleport * src, const CGTeleport * dst); @@ -303,7 +303,7 @@ class DLL_LINKAGE CGMonolith : public CGTeleport public: void onHeroVisit(const CGHeroInstance * h) const override; - void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const override; + void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const override; void initObj() override; template void serialize(Handler &h, const int version) @@ -329,7 +329,7 @@ class DLL_LINKAGE CGWhirlpool : public CGMonolith { public: void onHeroVisit(const CGHeroInstance * h) const override; - void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, std::vector exits) const override; + void teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer, TTeleportExitsList exits) const override; static bool isProtected( const CGHeroInstance * h ); template void serialize(Handler &h, const int version) From eb9f29e3684bc311e11ae49f6b5d8d607d50b609 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Wed, 2 Dec 2015 19:26:24 +0300 Subject: [PATCH 14/27] VCAI: restoring teleport probing feature for updated mechanics --- AI/VCAI/VCAI.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index e84694703..80c2f7f10 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -631,13 +631,18 @@ void VCAI::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exit if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit)) choosenExit = vstd::find_pos(exits, neededExit); -/* if(!status.channelProbing()) + if(!status.channelProbing()) { - vstd::copy_if(exits, vstd::set_inserter(teleportChannelProbingList), [&](ObjectInstanceID id) -> bool + for(auto exit : exits) { - return !(vstd::contains(visitableObjs, cb->getObj(id)) || id == choosenExit); - }); - }*/ + if(!vstd::contains(visitableObjs, cb->getObj(exit.first)) && + !vstd::contains(teleportChannelProbingList, exit.first) && + exit.first != destinationTeleport) + { + teleportChannelProbingList.push_back(exit.first); + } + } + } } requestActionASAP([=]() From 2f9ca778b27f7790bc25cbfd81eedbc836d417f0 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 3 Dec 2015 17:20:03 +0300 Subject: [PATCH 15/27] VCAI: add channel probing support for teleporters with multiple exits --- AI/VCAI/VCAI.cpp | 38 +++++++++++++++++++++++--------------- server/CGameHandler.cpp | 2 +- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 80c2f7f10..ee87c0f5d 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -625,22 +625,29 @@ void VCAI::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exit int choosenExit = -1; if(impassable) knownTeleportChannels[channel]->passability = TeleportChannel::IMPASSABLE; - else + else if(destinationTeleport != ObjectInstanceID() && destinationTeleportPos.valid()) { auto neededExit = std::make_pair(destinationTeleport, destinationTeleportPos); if(destinationTeleport != ObjectInstanceID() && vstd::contains(exits, neededExit)) choosenExit = vstd::find_pos(exits, neededExit); + } - if(!status.channelProbing()) + for(auto exit : exits) + { + if(status.channelProbing() && exit.first == destinationTeleport) { - for(auto exit : exits) + choosenExit = vstd::find_pos(exits, exit); + break; + } + else + { + // FIXME: This code generate "Object is not visible." errors + // What is better way to check that certain teleport exit wasn't visited yet or not visible? + if(!vstd::contains(visitableObjs, cb->getObj(exit.first)) && + !vstd::contains(teleportChannelProbingList, exit.first) && + exit.first != destinationTeleport) { - if(!vstd::contains(visitableObjs, cb->getObj(exit.first)) && - !vstd::contains(teleportChannelProbingList, exit.first) && - exit.first != destinationTeleport) - { - teleportChannelProbingList.push_back(exit.first); - } + teleportChannelProbingList.push_back(exit.first); } } } @@ -1867,11 +1874,11 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true), transit); }; - auto doTeleportMovement = [&](int3 dst, ObjectInstanceID exitId) + auto doTeleportMovement = [&](ObjectInstanceID exitId, int3 exitPos) { destinationTeleport = exitId; - destinationTeleportPos = CGHeroInstance::convertPosition(dst, true); - cb->moveHero(*h, destinationTeleportPos); + destinationTeleportPos = CGHeroInstance::convertPosition(exitPos, true); + cb->moveHero(*h, h->pos); destinationTeleport = ObjectInstanceID(); destinationTeleportPos = int3(); afterMovementCheck(); @@ -1880,13 +1887,14 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) auto doChannelProbing = [&]() -> void { auto currentExit = getObj(CGHeroInstance::convertPosition(h->pos,false), false); + auto currentExitPos = CGHeroInstance::convertPosition(h->pos,false); assert(currentExit); status.setChannelProbing(true); for(auto exit : teleportChannelProbingList) - doTeleportMovement(CGHeroInstance::convertPosition(h->pos,false), exit); + doTeleportMovement(exit, int3()); teleportChannelProbingList.clear(); - doTeleportMovement(CGHeroInstance::convertPosition(h->pos,false), currentExit->id); + doTeleportMovement(currentExit->id, currentExitPos); status.setChannelProbing(false); }; @@ -1900,7 +1908,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) auto nextObject = getObj(nextCoord, false); if(CGTeleport::isConnected(currentObject, nextObject)) { //we use special login if hero standing on teleporter it's mean we need - doTeleportMovement(currentCoord, nextObject->id); + doTeleportMovement(nextObject->id, nextCoord); if(teleportChannelProbingList.size()) doChannelProbing(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 7be5428e6..645a90b25 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1755,7 +1755,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo } logGlobal->traceStream() << "Player " << asker << " wants to move hero "<< hid.getNum() << " from "<< h->pos << " to " << dst; - const int3 hmpos = dst + int3(-1,0,0); + const int3 hmpos = CGHeroInstance::convertPosition(dst, false); if(!gs->map->isInTheMap(hmpos)) { From ee0874974328c8d45c116c6bfc91937788aceda8 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 3 Dec 2015 21:18:40 +0300 Subject: [PATCH 16/27] VCAI: more work on teleport exit probing --- AI/VCAI/VCAI.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index ee87c0f5d..cf99d0697 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -641,10 +641,10 @@ void VCAI::showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exit } else { - // FIXME: This code generate "Object is not visible." errors - // What is better way to check that certain teleport exit wasn't visited yet or not visible? - if(!vstd::contains(visitableObjs, cb->getObj(exit.first)) && - !vstd::contains(teleportChannelProbingList, exit.first) && + // TODO: Implement checking if visiting that teleport will uncovert any FoW + // So far this is the best option to handle decision about probing + auto obj = cb->getObj(exit.first, false); + if(obj == nullptr && !vstd::contains(teleportChannelProbingList, exit.first) && exit.first != destinationTeleport) { teleportChannelProbingList.push_back(exit.first); @@ -1877,7 +1877,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) auto doTeleportMovement = [&](ObjectInstanceID exitId, int3 exitPos) { destinationTeleport = exitId; - destinationTeleportPos = CGHeroInstance::convertPosition(exitPos, true); + if(exitPos.valid()) + destinationTeleportPos = CGHeroInstance::convertPosition(exitPos, true); cb->moveHero(*h, h->pos); destinationTeleport = ObjectInstanceID(); destinationTeleportPos = int3(); From 3800bd45b7f03396412ccfdff88d677efbdc8270 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 4 Dec 2015 01:54:25 +0300 Subject: [PATCH 17/27] Movement: initialize destinationTeleportPos with invalid int3 position --- AI/VCAI/VCAI.cpp | 8 ++++---- AI/VCAI/VCAI.h | 2 +- client/CPlayerInterface.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index cf99d0697..bd0b195b4 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -96,7 +96,7 @@ VCAI::VCAI(void) LOG_TRACE(logAi); makingTurn = nullptr; destinationTeleport = ObjectInstanceID(); - destinationTeleportPos = int3(); + destinationTeleportPos = int3(-1); } VCAI::~VCAI(void) @@ -1881,7 +1881,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) destinationTeleportPos = CGHeroInstance::convertPosition(exitPos, true); cb->moveHero(*h, h->pos); destinationTeleport = ObjectInstanceID(); - destinationTeleportPos = int3(); + destinationTeleportPos = int3(-1); afterMovementCheck(); }; @@ -1893,7 +1893,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) status.setChannelProbing(true); for(auto exit : teleportChannelProbingList) - doTeleportMovement(exit, int3()); + doTeleportMovement(exit, int3(-1)); teleportChannelProbingList.clear(); doTeleportMovement(currentExit->id, currentExitPos); status.setChannelProbing(false); @@ -2931,7 +2931,7 @@ void AIStatus::setMove(bool ongoing) void AIStatus::setChannelProbing(bool ongoing) { boost::unique_lock lock(mx); - ongoingHeroMovement = ongoing; + ongoingChannelProbing = ongoing; cv.notify_all(); } diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index aa5eb521b..25eb312cd 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -367,7 +367,7 @@ public: } else if(!h.saving) { - destinationTeleportPos = int3(); + destinationTeleportPos = int3(-1); } h & townVisitsThisWeek & lockedHeroes & reservedHeroesMap; //FIXME: cannot instantiate abstract class h & visitableObjs & alreadyVisited & reservedObjs; diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 6132d8035..089d94b88 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -97,7 +97,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player) { logGlobal->traceStream() << "\tHuman player interface for player " << Player << " being constructed"; destinationTeleport = ObjectInstanceID(); - destinationTeleportPos = int3(); + destinationTeleportPos = int3(-1); observerInDuelMode = false; howManyPeople++; GH.defActionsDef = 0; @@ -1416,7 +1416,7 @@ void CPlayerInterface::requestRealized( PackageApplied *pa ) && stillMoveHero.get() == DURING_MOVE) { // After teleportation via CGTeleport object is finished destinationTeleport = ObjectInstanceID(); - destinationTeleportPos = int3(); + destinationTeleportPos = int3(-1); stillMoveHero.setn(CONTINUE_MOVE); } } From 791d1e7ab479b0edb5ed81acbfb6b2e53521a41d Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 4 Dec 2015 05:30:43 +0300 Subject: [PATCH 18/27] VCAI: finish fixing of teleport probing for whirlpools --- AI/VCAI/VCAI.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index bd0b195b4..e94b5e49a 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1887,16 +1887,16 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) auto doChannelProbing = [&]() -> void { - auto currentExit = getObj(CGHeroInstance::convertPosition(h->pos,false), false); - auto currentExitPos = CGHeroInstance::convertPosition(h->pos,false); - assert(currentExit); + auto currentPos = CGHeroInstance::convertPosition(h->pos,false); + auto currentExit = getObj(currentPos, true)->id; status.setChannelProbing(true); for(auto exit : teleportChannelProbingList) doTeleportMovement(exit, int3(-1)); teleportChannelProbingList.clear(); - doTeleportMovement(currentExit->id, currentExitPos); status.setChannelProbing(false); + + doTeleportMovement(currentExit, currentPos); }; int i=path.nodes.size()-1; From eced16945e41c9dd4ebd0be4ea1f323e5f96ea0a Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 4 Dec 2015 05:43:55 +0300 Subject: [PATCH 19/27] getVisibleTeleportObjects: use getObj as not verbose We checking visibility in this function so there no need to send information about that into errorStream. --- lib/CGameInfoCallback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 56401514b..9d61db81f 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -853,7 +853,7 @@ std::vector CGameInfoCallback::getVisibleTeleportObjects(std:: { vstd::erase_if(ids, [&](ObjectInstanceID id) -> bool { - auto obj = getObj(id); + auto obj = getObj(id, false); return player != PlayerColor::UNFLAGGABLE && (!obj || !isVisible(obj->pos, player)); }); return ids; From 3b3c49420f85a9cf70c81237f98192469ba26286 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 4 Dec 2015 17:37:19 +0300 Subject: [PATCH 20/27] Thieves Guild: correct number of taverns to access information --- lib/CGameState.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 846ee32c7..21865b270 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2616,7 +2616,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) continue; const CGHeroInstance * best = statsHLP::findBestHero(this, g->second.color); InfoAboutHero iah; - iah.initFromHero(best, level >= 8); + iah.initFromHero(best, level >= 2); iah.army.clear(); tgi.colorToBestHero[g->second.color] = iah; } @@ -2633,27 +2633,27 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) { 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 + if(level >= 3) //obelisks found { FILL_FIELD(obelisks, CGObelisk::visited[gs->getPlayerTeam(g->second.color)->id]) } - if(level >= 5) //artifacts + if(level >= 4) //artifacts { FILL_FIELD(artifacts, statsHLP::getNumberOfArts(&g->second)) } - if(level >= 6) //army strength + if(level >= 4) //army strength { FILL_FIELD(army, statsHLP::getArmyStrength(&g->second)) } - if(level >= 7) //income + if(level >= 5) //income { FILL_FIELD(income, statsHLP::getIncome(&g->second)) } - if(level >= 8) //best hero's stats + if(level >= 2) //best hero's stats { //already set in lvl 1 handling } - if(level >= 9) //personality + if(level >= 3) //personality { for(auto g = players.cbegin(); g != players.cend(); ++g) { @@ -2670,7 +2670,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) } } - if(level >= 10) //best creature + if(level >= 4) //best creature { //best creatures belonging to player (highest AI value) for(auto g = players.cbegin(); g != players.cend(); ++g) From 2a63ba148ae9cdaa660b64e6e87812893bd0fef4 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 4 Dec 2015 18:38:57 +0300 Subject: [PATCH 21/27] Thieves Guild: fix tavern map object, lvl 0 when no taverns owned We also now check not number of towns, but only towns that has tavern built. Also according to original mechanics all taverns always display information based on your number of taverns and not number of taverns of object owner. --- client/windows/GUIClasses.cpp | 2 +- lib/CGameInfoCallback.cpp | 9 ++++++++- lib/CGameState.cpp | 6 ++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 3a52a6262..79456e908 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1630,9 +1630,9 @@ CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner): int counter = 0; for(auto & iter : tgi.colorToBestHero) { + new CPicture(colorToBox[iter.first.getNum()], 253 + 66 * counter, 334); if(iter.second.portrait >= 0) { - new CPicture(colorToBox[iter.first.getNum()], 253 + 66 * counter, 334); new CAnimImage("PortraitsSmall", iter.second.portrait, 0, 260 + 66 * counter, 360); //TODO: r-click info: // - r-click on hero diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index d51e9ac65..818f79ca3 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -197,7 +197,14 @@ void CGameInfoCallback::getThievesGuildInfo(SThievesGuildInfo & thi, const CGObj if(obj->ID == Obj::TOWN || obj->ID == Obj::TAVERN) { - gs->obtainPlayersStats(thi, gs->players[obj->tempOwner].towns.size()); + int taverns = 0; + for(auto town : gs->players[*player].towns) + { + if(town->hasBuilt(BuildingID::TAVERN)) + taverns++; + } + + gs->obtainPlayersStats(thi, taverns); } else if(obj->ID == Obj::DEN_OF_THIEVES) { diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 21865b270..ac3fccc0c 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2603,13 +2603,15 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) tgi.playerColors.push_back(elem.second.color); } - if(level >= 1) //num of towns & num of heroes + if(level >= 0) //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 + } + if(level >= 1) //best hero's portrait + { for(auto g = players.cbegin(); g != players.cend(); ++g) { if(playerInactive(g->second.color)) From bdc369ffba4b5d69474510af4d4c2bed41cb2e02 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 4 Dec 2015 21:08:09 +0300 Subject: [PATCH 22/27] Patrol: use manhattan distance for getting tiles in radius --- lib/CPathfinder.cpp | 2 +- lib/IGameCallback.cpp | 10 ++++++++-- lib/IGameCallback.h | 2 +- lib/int3.h | 7 ++++++- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 8046f461d..7f77683e9 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -596,7 +596,7 @@ void CPathfinder::initializePatrol() if(hero->patrol.patrolRadious) { state = PATROL_RADIUS; - gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadious); + gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadious, hero->tempOwner, 0, true); } else state = PATROL_LOCKED; diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 8f8f3b30c..cf335ba1b 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -45,7 +45,7 @@ void CPrivilagedInfoCallback::getFreeTiles (std::vector &tiles) const } } -void CPrivilagedInfoCallback::getTilesInRange( std::unordered_set &tiles, int3 pos, int radious, boost::optional player/*=uninit*/, int mode/*=0*/ ) const +void CPrivilagedInfoCallback::getTilesInRange( std::unordered_set &tiles, int3 pos, int radious, boost::optional player/*=uninit*/, int mode/*=0*/, bool patrolDistance/*=false*/) const { if(!!player && *player >= PlayerColor::PLAYER_LIMIT) { @@ -61,7 +61,13 @@ void CPrivilagedInfoCallback::getTilesInRange( std::unordered_set(pos.y - radious, 0); yd <= std::min(pos.y + radious, gs->map->height - 1); yd++) { - double distance = pos.dist2d(int3(xd,yd,pos.z)) - 0.5; + int3 tilePos(xd,yd,pos.z); + double distance; + if(patrolDistance) + distance = pos.mandist2d(tilePos); + else + distance = pos.dist2d(tilePos) - 0.5; + if(distance <= radious) { if(!player diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index b2ca42030..43c24d05c 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -30,7 +30,7 @@ class DLL_LINKAGE CPrivilagedInfoCallback : public CGameInfoCallback public: CGameState * gameState(); void getFreeTiles (std::vector &tiles) const; //used for random spawns - void getTilesInRange(std::unordered_set &tiles, int3 pos, int radious, boost::optional player = boost::optional(), int mode=0) const; //mode 1 - only unrevealed tiles; mode 0 - all, mode -1 - only unrevealed + void getTilesInRange(std::unordered_set &tiles, int3 pos, int radious, boost::optional player = boost::optional(), int mode = 0, bool patrolDistance = false) const; //mode 1 - only unrevealed tiles; mode 0 - all, mode -1 - only unrevealed void getAllTiles (std::unordered_set &tiles, boost::optional player = boost::optional(), int level=-1, int surface=0) const; //returns all tiles on given level (-1 - both levels, otherwise number of level); surface: 0 - land and water, 1 - only land, 2 - only water void pickAllowedArtsSet(std::vector &out); //gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant void getAllowedSpells(std::vector &out, ui16 level); diff --git a/lib/int3.h b/lib/int3.h index 7ac595183..a1b40ce55 100644 --- a/lib/int3.h +++ b/lib/int3.h @@ -105,6 +105,11 @@ public: { return std::sqrt((double)dist2dSQ(o)); } + //manhattan distance used for patrol radius (z coord is not used) + double mandist2d(const int3 & o) const + { + return abs(o.x - x) + abs(o.y - y); + } bool areNeighbours(const int3 & o) const { @@ -175,4 +180,4 @@ int3 findClosestTile (Container & container, int3 dest) } } return result; -} \ No newline at end of file +} From 82c4c2f859dc8b8947fa7984e6ab634e5a590caf Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 4 Dec 2015 21:36:45 +0300 Subject: [PATCH 23/27] Patrol: update changelog --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index 698c2b1e2..0dbdfb992 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,7 @@ ADVETURE AI: * Fixed AI trying to go through underground rock * Fixed several cases causing AI wandering aimlessly * AI can again pick best artifacts and exchange artifacts between heroes +* AI heroes with patrol enabled won't leave patrol area anymore RANDOM MAP GENERATOR: * Changed fractalization algorithm so it can create cycles From d856fde73f3ff1ad863cb2b21787ff750cc7d8b7 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 4 Dec 2015 22:58:14 +0300 Subject: [PATCH 24/27] Rumors: use enum instead of magic numbers and avoid potential crash --- lib/CGameInfoCallback.cpp | 10 +++++----- lib/CGameState.cpp | 35 +++++++++++++++++++---------------- lib/CGameState.h | 13 +++++++++++-- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 818f79ca3..14062fa18 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -575,14 +575,14 @@ EPlayerStatus::EStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bo std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTavern) const { std::string text = "", extraText = ""; - if(gs->rumor.type == RumorState::RUMOR_NONE) // (version < 755 backward compatability + if(gs->rumor.type == RumorState::TYPE_NONE) // (version < 755 backward compatability return text; auto rumor = gs->rumor.last[gs->rumor.type]; switch(gs->rumor.type) { - case RumorState::RUMOR_STATS: - if(rumor.first == 212) + case RumorState::TYPE_SPECIAL: + if(rumor.first == RumorState::RUMOR_GRAIL) extraText = VLC->generaltexth->arraytxt[158 + rumor.second]; else extraText = VLC->generaltexth->capColors[rumor.second]; @@ -590,11 +590,11 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav text = boost::str(boost::format(VLC->generaltexth->allTexts[rumor.first]) % extraText); break; - case RumorState::RUMOR_MAP: + case RumorState::TYPE_MAP: text = gs->map->rumors[rumor.first].text; break; - case RumorState::RUMOR_RAND: + case RumorState::TYPE_RAND: text = VLC->generaltexth->tavernRumors[rumor.first]; break; } diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index ac3fccc0c..bad05ba3a 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2135,25 +2135,28 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const void CGameState::updateRumor() { - static std::vector rumorTypes = {RumorState::RUMOR_MAP, RumorState::RUMOR_STATS, RumorState::RUMOR_RAND, RumorState::RUMOR_RAND}; - static std::vector statsRumorTypes = {208, 209, 210, 211, 212}; + static std::vector rumorTypes = {RumorState::TYPE_MAP, RumorState::TYPE_SPECIAL, RumorState::TYPE_RAND, RumorState::TYPE_RAND}; + std::vector sRumorTypes = { + RumorState::RUMOR_OBELISKS, RumorState::RUMOR_ARTIFACTS, RumorState::RUMOR_ARMY, RumorState::RUMOR_INCOME}; + if(map->grailPos.valid()) // Grail should always be on map, but I had related crash I didn't manage to reproduce + sRumorTypes.push_back(RumorState::RUMOR_GRAIL); int rumorId = -1, rumorExtra = -1; - auto & rand = gs->getRandomGenerator(); + auto & rand = getRandomGenerator(); rumor.type = *RandomGeneratorUtil::nextItem(rumorTypes, rand); - if(!gs->map->rumors.size() && rumor.type == RumorState::RUMOR_MAP) - rumor.type = RumorState::RUMOR_RAND; + if(!map->rumors.size() && rumor.type == RumorState::TYPE_MAP) + rumor.type = RumorState::TYPE_RAND; do { switch(rumor.type) { - case RumorState::RUMOR_STATS: + case RumorState::TYPE_SPECIAL: { SThievesGuildInfo tgi; - gs->obtainPlayersStats(tgi, 20); - rumorId = *RandomGeneratorUtil::nextItem(statsRumorTypes, rand); - if(rumorId == 212) + obtainPlayersStats(tgi, 20); + rumorId = *RandomGeneratorUtil::nextItem(sRumorTypes, rand); + if(rumorId == RumorState::RUMOR_GRAIL) { rumorExtra = getTile(map->grailPos)->terType; break; @@ -2162,19 +2165,19 @@ void CGameState::updateRumor() std::vector players = {}; switch(rumorId) { - case 208: + case RumorState::RUMOR_OBELISKS: players = tgi.obelisks[0]; break; - case 209: + case RumorState::RUMOR_ARTIFACTS: players = tgi.artifacts[0]; break; - case 210: + case RumorState::RUMOR_ARMY: players = tgi.army[0]; break; - case 211: + case RumorState::RUMOR_INCOME: players = tgi.income[0]; break; } @@ -2182,12 +2185,12 @@ void CGameState::updateRumor() break; } - case RumorState::RUMOR_MAP: - rumorId = rand.nextInt(gs->map->rumors.size() - 1); + case RumorState::TYPE_MAP: + rumorId = rand.nextInt(map->rumors.size() - 1); break; - case RumorState::RUMOR_RAND: + case RumorState::TYPE_RAND: do { rumorId = rand.nextInt(VLC->generaltexth->tavernRumors.size() - 1); diff --git a/lib/CGameState.h b/lib/CGameState.h index cab3f572b..76c36363a 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -212,13 +212,22 @@ struct DLL_LINKAGE RumorState { enum ERumorType : ui8 { - RUMOR_NONE = 0, RUMOR_RAND, RUMOR_STATS, RUMOR_MAP + TYPE_NONE = 0, TYPE_RAND, TYPE_SPECIAL, TYPE_MAP + }; + + enum ERumorTypeSpecial : ui8 + { + RUMOR_OBELISKS = 208, + RUMOR_ARTIFACTS = 209, + RUMOR_ARMY = 210, + RUMOR_INCOME = 211, + RUMOR_GRAIL = 212 }; ERumorType type; std::map> last; - RumorState(){type = RUMOR_NONE; last = {};}; + RumorState(){type = TYPE_NONE; last = {};}; bool update(int id, int extra); template void serialize(Handler &h, const int version) From 01c5bc25e519209991fb3b49967d5a1af88848d5 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 5 Dec 2015 01:14:03 +0300 Subject: [PATCH 25/27] Move TTeleportExitsList to CObjectHandler.h Not a perfect solution, but this is the best location for now. --- lib/CGameInterface.h | 2 +- lib/mapObjects/CObjectHandler.h | 4 ++++ lib/mapObjects/MiscObjects.h | 2 -- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index b120d95d7..a49b5894f 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -7,7 +7,7 @@ #include "spells/ViewSpellInt.h" -#include "mapObjects/MiscObjects.h" +#include "mapObjects/CObjectHandler.h" /* * CGameInterface.h, part of VCMI engine diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index adc71b3d1..b159482c8 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -22,6 +22,10 @@ class CGObjectInstance; struct MetaString; struct BattleResult; +// This one teleport-specific, but has to be available everywhere in callbacks and netpacks +// For now it's will be there till teleports code refactored and moved into own file +typedef std::vector> TTeleportExitsList; + class DLL_LINKAGE IObjectInterface { public: diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index 161fe38e7..6ae8650de 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -247,8 +247,6 @@ public: ui32 defaultResProduction(); }; -typedef std::vector> TTeleportExitsList; - struct DLL_LINKAGE TeleportChannel { enum EPassability {UNKNOWN, IMPASSABLE, PASSABLE}; From 82ce58eceb0ae9f9104622cfbb34980adade84e8 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 5 Dec 2015 01:45:10 +0300 Subject: [PATCH 26/27] CPathfinder::initializePatrol: don't pass valid PlayerColor It's break getTilesInRange with mode 0 and we don't really need to check visibility anyway. --- lib/CPathfinder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 7f77683e9..55ac78edb 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -596,7 +596,7 @@ void CPathfinder::initializePatrol() if(hero->patrol.patrolRadious) { state = PATROL_RADIUS; - gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadious, hero->tempOwner, 0, true); + gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadious, boost::optional(), 0, true); } else state = PATROL_LOCKED; From 77088101483508577b0970a353923515f046dfd8 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 5 Dec 2015 08:08:02 +0300 Subject: [PATCH 27/27] VCAI: don't serialize destinationTeleportPos to avoid crash This won't affect AI functionality except if game saved while AI moving through teleporters. Serialization for some reason don't work properly and cause save loading to fail. --- AI/VCAI/VCAI.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index a208d0785..6963b8c79 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -345,14 +345,6 @@ public: template void serializeInternal(Handler &h, const int version) { h & knownTeleportChannels & knownSubterraneanGates & destinationTeleport; - if(version >= 755) - { - h & destinationTeleportPos; - } - else if(!h.saving) - { - destinationTeleportPos = int3(-1); - } h & townVisitsThisWeek & lockedHeroes & reservedHeroesMap; //FIXME: cannot instantiate abstract class h & visitableObjs & alreadyVisited & reservedObjs; h & saving & status & battlename;