From 6c0c03d74b12f17d93c31f3baad3ff8f237b01e0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 21 Sep 2014 16:42:08 +0300 Subject: [PATCH] Refactoing of pathfinder <-> client/AI interaction to remove dependency on selected hero - finished removal of server-side setSelection - disabled some broken code (AI & cheats). TODO: fix --- AI/VCAI/AIUtility.cpp | 21 ++++----- AI/VCAI/AIUtility.h | 11 ++++- AI/VCAI/Fuzzy.cpp | 2 +- AI/VCAI/Goals.cpp | 9 ++-- AI/VCAI/VCAI.cpp | 47 ++++++++++--------- AI/VCAI/VCAI.h | 4 ++ CCallback.cpp | 70 +++-------------------------- CCallback.h | 16 ++----- client/CPlayerInterface.cpp | 24 +++++----- client/CPlayerInterface.h | 5 ++- client/Client.cpp | 25 ++++++----- client/Client.h | 9 ++-- client/NetPacksClient.cpp | 4 +- client/windows/CAdvmapInterface.cpp | 17 ++++--- lib/CGameInterface.h | 4 +- lib/CGameState.cpp | 46 ++++++++++++------- lib/CGameState.h | 48 +------------------- lib/CGameStateFwd.h | 46 +++++++++++++++++++ server/CGameHandler.cpp | 32 ++++++------- 19 files changed, 206 insertions(+), 234 deletions(-) diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index 887f4757a..9b2a0c605 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -175,9 +175,11 @@ std::string strFromInt3(int3 pos) return oss.str(); } -bool isCloser(const CGObjectInstance *lhs, const CGObjectInstance *rhs) +bool CDistanceSorter::operator ()(const CGObjectInstance *lhs, const CGObjectInstance *rhs) { - const CGPathNode *ln = cb->getPathInfo(lhs->visitablePos()), *rn = cb->getPathInfo(rhs->visitablePos()); + const CGPathNode *ln = ai->myCb->getPathsInfo(hero)->getPathInfo(lhs->visitablePos()), + *rn = ai->myCb->getPathsInfo(hero)->getPathInfo(rhs->visitablePos()); + if(ln->turns != rn->turns) return ln->turns < rn->turns; @@ -340,11 +342,6 @@ bool isSafeToVisit(HeroPtr h, crint3 tile) return true; //there's no danger } -bool isReachable(const CGObjectInstance *obj) -{ - return cb->getPathInfo(obj->visitablePos())->turns < 255; -} - bool canBeEmbarkmentPoint(const TerrainTile *t, bool fromWater) { //tile must be free of with unoccupied boat @@ -356,7 +353,7 @@ bool canBeEmbarkmentPoint(const TerrainTile *t, bool fromWater) int3 whereToExplore(HeroPtr h) { TimeCheck tc ("where to explore"); - cb->setSelection(*h); + ai->setSelection(*h); int radius = h->getSightRadious(); int3 hpos = h->visitablePos(); @@ -371,7 +368,7 @@ int3 whereToExplore(HeroPtr h) { int3 op = obj->visitablePos(); CGPath p; - cb->getPath2(op, p); + ai->myCb->getPathsInfo(h.get())->getPath(op, p); if (p.nodes.size() && p.endPos() == op && p.nodes.size() <= DIST_LIMIT) if (ai->isGoodForVisit(obj, h)) nearbyVisitableObjs.push_back(obj); @@ -379,7 +376,7 @@ int3 whereToExplore(HeroPtr h) } } vstd::removeDuplicates (nearbyVisitableObjs); //one object may occupy multiple tiles - boost::sort(nearbyVisitableObjs, isCloser); + boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get())); if(nearbyVisitableObjs.size()) return nearbyVisitableObjs.back()->visitablePos(); @@ -396,8 +393,8 @@ int3 whereToExplore(HeroPtr h) bool isBlockedBorderGate(int3 tileToHit) { - return cb->getTile(tileToHit)->topVisitableId() == Obj::BORDER_GATE - && cb->getPathInfo(tileToHit)->accessible != CGPathNode::ACCESSIBLE; + return cb->getTile(tileToHit)->topVisitableId() == Obj::BORDER_GATE && + (dynamic_cast (cb->getTile(tileToHit)->visitableObjects.back()))->wasMyColorVisited (ai->playerID); } int howManyTilesWillBeDiscovered(const int3 &pos, int radious, CCallback * cbp) diff --git a/AI/VCAI/AIUtility.h b/AI/VCAI/AIUtility.h index 91116d890..61ee3c068 100644 --- a/AI/VCAI/AIUtility.h +++ b/AI/VCAI/AIUtility.h @@ -146,8 +146,6 @@ void getVisibleNeighbours(const std::vector &tiles, std::vector &out bool canBeEmbarkmentPoint(const TerrainTile *t, bool fromWater); bool isBlockedBorderGate(int3 tileToHit); -bool isReachable(const CGObjectInstance *obj); -bool isCloser(const CGObjectInstance *lhs, const CGObjectInstance *rhs); bool isWeeklyRevisitable (const CGObjectInstance * obj); bool shouldVisit (HeroPtr h, const CGObjectInstance * obj); @@ -162,3 +160,12 @@ 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); + +class CDistanceSorter +{ + const CGHeroInstance * hero; +public: + CDistanceSorter(const CGHeroInstance * hero): hero(hero) {} + + bool operator ()(const CGObjectInstance *lhs, const CGObjectInstance *rhs); +}; diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index db173016b..23037a2c5 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -405,7 +405,7 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g) return 0; //assert(cb->isInTheMap(g.tile)); - cb->setSelection (g.hero.h); + ai->setSelection (g.hero.h); float turns = 0; float distance = cb->getMovementCost(g.hero.h, g.tile); if (!distance) //we stand on that tile diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index b4929f180..e4e1d05e0 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -256,7 +256,7 @@ TSubgoal Win::whatToDoToAchieve() return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL); }), towns.end()); - boost::sort(towns, isCloser); + boost::sort(towns, CDistanceSorter(h.get())); if(towns.size()) { return sptr (Goals::VisitTile(towns.front()->visitablePos()).sethero(h)); @@ -353,7 +353,7 @@ TSubgoal FindObj::whatToDoToAchieve() } } } - if (o && isReachable(o)) //we don't use isAccessibleForHero as we don't know which hero it is + if (o)// FIXME: re-enable with *some* hero && isReachable(o)) //we don't use isAccessibleForHero as we don't know which hero it is return sptr (Goals::GetObj(o->id.getNum())); else return sptr (Goals::Explore()); @@ -377,7 +377,7 @@ TSubgoal GetObj::whatToDoToAchieve() } else { - if (isReachable(obj)) + //if (isReachable(obj)) //FIXME: re-enable with some hero return sptr (Goals::VisitTile(pos).sethero(hero)); //we must visit object with same hero, if any } return sptr (Goals::ClearWayTo(pos).sethero(hero)); @@ -853,7 +853,8 @@ TSubgoal GatherTroops::whatToDoToAchieve() } if (dwellings.size()) { - boost::sort(dwellings, isCloser); + //FIXME: we need hero to sort dwellings by distance + //boost::sort(dwellings, CDistanceSorter(h.get())); return sptr (Goals::GetObj(dwellings.front()->id.getNum())); } else diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 68dec5ed8..c105214f4 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -95,6 +95,7 @@ VCAI::VCAI(void) { LOG_TRACE(logAi); makingTurn = nullptr; + currentSelection = nullptr; } VCAI::~VCAI(void) @@ -664,9 +665,6 @@ void VCAI::makeTurn() } break; } - //FIXME: necessary? How to re-enable? - //cb->recalculatePaths(cb->getSelectedHero()); - markHeroAbleToExplore (primaryHero()); makeTurnInternal(); @@ -697,9 +695,9 @@ void VCAI::makeTurnInternal() continue; } - cb->setSelection(hero.first.get()); + setSelection(hero.first.get()); std::vector vec(hero.second.begin(), hero.second.end()); - boost::sort (vec, isCloser); + boost::sort (vec, CDistanceSorter(hero.first.get())); for (auto obj : vec) { if(!obj || !cb->getObj(obj->id)) @@ -1222,7 +1220,7 @@ std::vector VCAI::getPossibleDestinations(HeroPtr h) } } - boost::sort(possibleDestinations, isCloser); + boost::sort(possibleDestinations, CDistanceSorter(h.get())); return possibleDestinations; } @@ -1256,7 +1254,7 @@ bool VCAI::canRecruitAnyHero (const CGTownInstance * t) const void VCAI::wander(HeroPtr h) { - cb->setSelection(*h); + setSelection(*h); //unclaim objects that are now dangerous for us auto reservedObjsSetCopy = reservedHeroesMap[h]; for (auto obj : reservedObjsSetCopy) @@ -1381,7 +1379,7 @@ void VCAI::wander(HeroPtr h) erase_if(dests, shouldBeErased); erase_if_present(dests, dest); //why that fails sometimes when removing monsters? - boost::sort(dests, isCloser); //find next closest one + boost::sort(dests, CDistanceSorter(h.get())); //find next closest one } if (h->visitedTown) @@ -1618,7 +1616,6 @@ const CGObjectInstance * VCAI::getUnvisitedObj(const std::functionsetSelection(*h); if (!includeAllies) { //don't visit tile occupied by allied hero for (auto obj : cb->getVisitableObjs(pos)) @@ -1629,12 +1626,12 @@ bool VCAI::isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies / return false; } } - return cb->getPathInfo(pos)->reachable(); + return cb->getPathsInfo(h.get())->getPathInfo(pos)->reachable(); } bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) { - cb->setSelection(h.h); //make sure we are using the RIGHT pathfinder + setSelection(h.h); //make sure we are using the RIGHT pathfinder logAi->debugStream() << boost::format("Moving hero %s to tile %s") % h->name % dst; int3 startHpos = h->visitablePos(); bool ret = false; @@ -1649,11 +1646,10 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) else { CGPath path; - cb->getPath2(dst, path); + cb->getPathsInfo(h.get())->getPath(dst, path); if(path.nodes.empty()) { logAi->errorStream() << "Hero " << h->name << " cannot reach " << dst; - cb->recalculatePaths(h.get()); throw goalFulfilledException (sptr(Goals::VisitTile(dst).sethero(h))); } @@ -1702,7 +1698,6 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) reserveObject(h, obj); } - cb->recalculatePaths(h.get()); if (startHpos == h->visitablePos() && !ret) //we didn't move and didn't reach the target { erase_if_present (lockedHeroes, h); //hero seemingly is confused @@ -2190,6 +2185,16 @@ void VCAI::striveToQuest (const QuestInfo &q) } } +const CArmedInstance * VCAI::getSelection() +{ + return currentSelection; +} + +void VCAI::setSelection(const CArmedInstance * obj) +{ + currentSelection = obj; +} + void VCAI::performTypicalActions() { for(auto h : getUnblockedHeroes()) @@ -2230,7 +2235,7 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) auto best = dstToRevealedTiles.begin(); for (auto i = dstToRevealedTiles.begin(); i != dstToRevealedTiles.end(); i++) { - const CGPathNode *pn = cb->getPathInfo(i->first); + const CGPathNode *pn = cb->getPathsInfo(h.get())->getPathInfo(i->first); //const TerrainTile *t = cb->getTile(i->first); if(best->second < i->second && pn->reachable() && pn->accessible == CGPathNode::ACCESSIBLE) best = i; @@ -2245,7 +2250,7 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) int3 VCAI::explorationNewPoint(HeroPtr h) { //logAi->debugStream() << "Looking for an another place for exploration..."; - cb->setSelection(h.h); + setSelection(h.h); int radius = h->getSightRadious(); std::vector > tiles; //tiles[distance_to_fow] @@ -2269,13 +2274,11 @@ int3 VCAI::explorationNewPoint(HeroPtr h) for(const int3 &tile : tiles[i]) { - if (cbp->getTile(tile)->blocked) //does it shorten the time? - continue; - if (!cbp->getPathInfo(tile)->reachable()) //this will remove tiles that are guarded by monsters (or removable objects) + if (!cb->getPathsInfo(h.get())->getPathInfo(tile)->reachable()) //this will remove tiles that are guarded by monsters (or removable objects) continue; CGPath path; - cbp->getPath2(tile, path); + cb->getPathsInfo(h.get())->getPath(tile, path); float ourValue = (float)howManyTilesWillBeDiscovered(tile, radius, cbp) / (path.nodes.size() + 1); //+1 prevents erratic jumps if (ourValue > bestValue) //avoid costly checks of tiles that don't reveal much @@ -2655,7 +2658,7 @@ SectorMap::SectorMap() SectorMap::SectorMap(HeroPtr h) { - cb->setSelection(h.h); + ai->setSelection(h.h); update(); makeParentBFS(h->visitablePos()); } @@ -3104,7 +3107,7 @@ int3 SectorMap::findFirstVisitableTile (HeroPtr h, crint3 dst) logAi->warnStream() << ("Another allied hero stands in our way"); return ret; } - if(cb->getPathInfo(curtile)->reachable()) + if(ai->myCb->getPathsInfo(h.get())->getPathInfo(curtile)->reachable()) { return curtile; } diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 85feffc35..c1660aa00 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -124,6 +124,7 @@ const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, Bu class VCAI : public CAdventureAI { + const CArmedInstance * currentSelection; public: //internal methods for town development @@ -258,6 +259,9 @@ public: void completeGoal (Goals::TSubgoal goal); //safely removes goal from reserved hero void striveToQuest (const QuestInfo &q); + const CArmedInstance * getSelection(); + void setSelection(const CArmedInstance * obj); + void recruitHero(const CGTownInstance * t, bool throwing = false); bool isGoodForVisit(const CGObjectInstance *obj, HeroPtr h); std::vector getPossibleDestinations(HeroPtr h); diff --git a/CCallback.cpp b/CCallback.cpp index 8aaef9dab..15fa6e869 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -235,31 +235,6 @@ void CCallback::setFormation(const CGHeroInstance * hero, bool tight) sendRequest(&pack); } -void CCallback::setSelection(const CArmedInstance * obj) -{/* - if(!player || obj->getOwner() != *player) - { - logGlobal->errorStream() << boost::format("Cannot set selection to the object that is not owned. Object owner is %s, callback player %s") % obj->getOwner() % player; - return; - } - - if(obj->getOwner() != *player) - { - // Cf. bug #1679 http://bugs.vcmi.eu/view.php?id=1679 - logGlobal->warnStream() << "The selection request became invalid because of event that occurred after it was made. Object owner is now " << obj->getOwner(); - throw std::runtime_error("setSelection not allowed"); - } - - if(obj->ID == Obj::HERO) - { - if(cl->pathInfo->hero != obj) //calculate new paths only if we selected a different hero - cl->calculatePaths(static_cast(obj)); - - //nasty workaround. TODO: nice workaround - cl->gs->getPlayer(*player)->currentSelection = obj->id; - }*/ -} - void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero) { assert(townOrTavern); @@ -309,47 +284,22 @@ CCallback::~CCallback() //trivial, but required. Don`t remove. } - -const CGPathNode * CCallback::getPathInfo( int3 tile ) -{ - if (!gs->map->isInTheMap(tile)) - return nullptr; - - validatePaths(); - return &cl->pathInfo->nodes[tile.x][tile.y][tile.z]; -} - -int CCallback::getDistance( int3 tile ) -{ - CGPath ret; - if (getPath2 (tile, ret)) - return ret.nodes.size(); - else - return 255; -} - bool CCallback::canMoveBetween(const int3 &a, const int3 &b) { //TODO: merge with Pathfinder::canMoveBetween return gs->checkForVisitableDir(a, b) && gs->checkForVisitableDir(b, a); } -bool CCallback::getPath2( int3 dest, CGPath &ret ) -{ - if (!gs->map->isInTheMap(dest)) - return false; - - validatePaths(); - - boost::unique_lock pathLock(cl->pathMx); - return cl->pathInfo->getPath(dest, ret); -} - int CCallback::getMovementCost(const CGHeroInstance * hero, int3 dest) { return gs->getMovementCost(hero, hero->visitablePos(), dest, hero->hasBonusOfType (Bonus::FLYING_MOVEMENT), hero->movement); } +const CPathsInfo * CCallback::getPathsInfo(const CGHeroInstance *h) +{ + return cl->getPathsInfo(h); +} + int3 CCallback::getGuardingCreaturePosition(int3 tile) { if (!gs->map->isInTheMap(tile)) @@ -358,15 +308,9 @@ int3 CCallback::getGuardingCreaturePosition(int3 tile) return gs->map->guardingCreaturePositions[tile.x][tile.y][tile.z]; } -void CCallback::recalculatePaths(const CGHeroInstance * hero) +void CCallback::calculatePaths( const CGHeroInstance *hero, CPathsInfo &out) { - if (hero) - cl->calculatePaths(hero); -} - -void CCallback::calculatePaths( const CGHeroInstance *hero, CPathsInfo &out, int3 src /*= int3(-1,-1,-1)*/, int movement /*= -1*/ ) -{ - gs->calculatePaths(hero, out, src, movement); + gs->calculatePaths(hero, out); } void CCallback::dig( const CGObjectInstance *hero ) diff --git a/CCallback.h b/CCallback.h index beccba04c..dc866ffe6 100644 --- a/CCallback.h +++ b/CCallback.h @@ -72,8 +72,6 @@ public: virtual void endTurn()=0; virtual void buyArtifact(const CGHeroInstance *hero, ArtifactID aid)=0; //used to buy artifacts in towns (including spell book in the guild and war machines in blacksmith) virtual void setFormation(const CGHeroInstance * hero, bool tight)=0; - virtual void setSelection(const CArmedInstance * obj)=0; - virtual void save(const std::string &fname) = 0; virtual void sendMessage(const std::string &mess) = 0; @@ -100,24 +98,17 @@ public: class CCallback : public CPlayerSpecificInfoCallback, public IGameActionCallback, public CBattleCallback { -private: - - void validatePaths(); //recalcualte paths if necessary - public: CCallback(CGameState * GS, boost::optional Player, CClient *C); virtual ~CCallback(); //client-specific functionalities (pathfinding) - virtual const CGPathNode *getPathInfo(int3 tile); //uses main, client pathfinder info - virtual int getDistance(int3 tile); - virtual bool getPath2(int3 dest, CGPath &ret); //uses main, client pathfinder info virtual bool canMoveBetween(const int3 &a, const int3 &b); virtual int getMovementCost(const CGHeroInstance * hero, int3 dest); - virtual int3 getGuardingCreaturePosition(int3 tile); //uses main, client pathfinder info + virtual int3 getGuardingCreaturePosition(int3 tile); + virtual const CPathsInfo * getPathsInfo(const CGHeroInstance *h); - virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1); - virtual void recalculatePaths(const CGHeroInstance * hero); //updates main, client pathfinder info (should be called when moving hero is over) + virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out); //Set of metrhods that allows adding more interfaces for this player that'll receive game event call-ins. void registerGameInterface(shared_ptr gameEvents); @@ -150,7 +141,6 @@ public: void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override; void trade(const CGObjectInstance *market, EMarketMode::EMarketMode mode, int id1, int id2, int val1, const CGHeroInstance *hero = nullptr); void setFormation(const CGHeroInstance * hero, bool tight); - void setSelection(const CArmedInstance * obj); void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero); void save(const std::string &fname); void sendMessage(const std::string &mess); diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 4a1881b16..dc934b148 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -105,6 +105,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player) curAction = nullptr; playerID=Player; human=true; + currentSelection = nullptr; castleInt = nullptr; battleInt = nullptr; //pim = new boost::recursive_mutex; @@ -1256,12 +1257,10 @@ template void CPlayerInterface::serializeTempl( Handler &h, c { h & pathsMap; - CPathsInfo pathsInfo(cb->getMapSize()); for(auto &p : pathsMap) { - cb->calculatePaths(p.first, pathsInfo); CGPath path; - pathsInfo.getPath(p.second, path); + cb->getPathsInfo(p.first)->getPath(p.second, path); paths[p.first] = path; logGlobal->traceStream() << boost::format("Restored path for hero %s leading to %s with %d nodes") % p.first->nodeName() % p.second % path.nodes.size(); @@ -1552,6 +1551,16 @@ bool CPlayerInterface::ctrlPressed() const return isCtrlKeyDown(); } +const CArmedInstance * CPlayerInterface::getSelection() +{ + return currentSelection; +} + +void CPlayerInterface::setSelection(const CArmedInstance * obj) +{ + currentSelection = obj; +} + void CPlayerInterface::update() { if (!locked) @@ -2185,13 +2194,6 @@ void CPlayerInterface::eraseCurrentPathOf( const CGHeroInstance * ho, bool check adventureInt->updateMoveHero(ho, false); } -void CPlayerInterface::updateCurrentHeroPath() -{ - //TODO? lazy evaluation? paths now can get recalculated multiple times upon various game events - if (currentSelection)//if we have selected hero... - calculatePaths(currentSelection); -} - void CPlayerInterface::removeLastNodeFromPath(const CGHeroInstance *ho) { adventureInt->terrain.currentPath->nodes.erase(adventureInt->terrain.currentPath->nodes.end()-1); @@ -2213,7 +2215,7 @@ CGPath * CPlayerInterface::getAndVerifyPath(const CGHeroInstance * h) { assert(h->getPosition(false) == path.startPos()); //update the hero path in case of something has changed on map - if(LOCPLINT->cb->getPath2(path.endPos(), path)) + if(LOCPLINT->cb->getPathsInfo(h)->getPath(path.endPos(), path)) return &path; else paths.erase(h); diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index 12ac2f669..de525253b 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -86,7 +86,7 @@ enum /// Central class for managing user interface logic class CPlayerInterface : public CGameInterface, public ILockedUpdatable { - const CGObjectInstance * currentSelection; + const CArmedInstance * currentSelection; public: bool observerInDuelMode; @@ -118,6 +118,8 @@ public: shared_ptr autofightingAI; //AI that makes decisions bool isAutoFightOn; //Flag, switch it to stop quick combat. Don't touch if there is no battle interface. + const CArmedInstance * getSelection(); + void setSelection(const CArmedInstance * obj); struct SpellbookLastSetting { @@ -247,7 +249,6 @@ public: void movementPxStep( const TryMoveHero &details, int i, const int3 &hp, const CGHeroInstance * ho );//performing step of movement void finishMovement( const TryMoveHero &details, const int3 &hp, const CGHeroInstance * ho ); //finish movement void eraseCurrentPathOf( const CGHeroInstance * ho, bool checkForExistanceOfPath = true ); - void updateCurrentHeroPath(); void removeLastNodeFromPath(const CGHeroInstance *ho); CGPath *getAndVerifyPath( const CGHeroInstance * h ); diff --git a/client/Client.cpp b/client/Client.cpp index 4037c6e36..f4d479375 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -664,13 +664,6 @@ PlayerColor CClient::getLocalPlayer() const return getCurrentPlayer(); } -void CClient::calculatePaths(const CGHeroInstance *h) -{ - assert(h); - boost::unique_lock pathLock(pathMx); - gs->calculatePaths(h, *pathInfo); -} - void CClient::commenceTacticPhaseForInt(shared_ptr battleInt) { setThreadName("CClient::commenceTacticPhaseForInt"); @@ -685,10 +678,22 @@ void CClient::commenceTacticPhaseForInt(shared_ptr battleI } HANDLE_EXCEPTION } -void CClient::invalidatePaths(const CGHeroInstance *h /*= nullptr*/) +void CClient::invalidatePaths() { - if(!h || pathInfo->hero == h) - pathInfo->isValid = false; + // turn pathfinding info into invalid. It will be regenerated later + boost::unique_lock pathLock(pathInfo->pathMx); + pathInfo->hero = nullptr; +} + +const CPathsInfo * CClient::getPathsInfo(const CGHeroInstance *h) +{ + assert(h); + boost::unique_lock pathLock(pathInfo->pathMx); + if (pathInfo->hero != h) + { + gs->calculatePaths(h, *pathInfo.get()); + } + return pathInfo.get(); } int CClient::sendRequest(const CPack *request, PlayerColor player) diff --git a/client/Client.h b/client/Client.h index aaafcc9bd..a099d1007 100644 --- a/client/Client.h +++ b/client/Client.h @@ -113,6 +113,7 @@ public: /// Class which handles client - server logic class CClient : public IGameCallback { + unique_ptr pathInfo; public: std::map > callbacks; //callbacks given to player interfaces std::map > battleCallbacks; //callbacks given to player interfaces @@ -129,9 +130,6 @@ public: boost::optional curbaction; - unique_ptr pathInfo; - boost::mutex pathMx; //protects the variable above - CScriptingModule *erm; ThreadSafeVector waitingRequest; @@ -158,8 +156,9 @@ public: void campaignMapFinished( shared_ptr camp ); void finishCampaign( shared_ptr camp ); void proposeNextMission(shared_ptr camp); - void invalidatePaths(const CGHeroInstance *h = nullptr); //invalidates paths for hero h or for any hero if h is nullptr => they'll got recalculated when the next query comes - void calculatePaths(const CGHeroInstance *h); + + void invalidatePaths(); + const CPathsInfo * getPathsInfo(const CGHeroInstance *h); bool terminate; // tell to terminate boost::thread *connectionHandler; //thread running run() method diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 3cf112271..e3a6639cd 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -160,7 +160,7 @@ void SetMana::applyCl( CClient *cl ) void SetMovePoints::applyCl( CClient *cl ) { const CGHeroInstance *h = cl->getHero(hid); - cl->invalidatePaths(h); + cl->invalidatePaths(); INTERFACE_CALL_IF_PRESENT(h->tempOwner, heroMovePointsChanged, h); } @@ -905,7 +905,7 @@ void CenterView::applyCl(CClient *cl) void NewObject::applyCl(CClient *cl) { - INTERFACE_CALL_IF_PRESENT(player, updateCurrentHeroPath); + cl->invalidatePaths(); const CGObjectInstance *obj = cl->getObj(id); CGI->mh->printObject(obj); diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index cf9092d28..7aa2ddaf7 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -948,7 +948,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key) CGPath &path = LOCPLINT->paths[h]; terrain.currentPath = &path; - if(!LOCPLINT->cb->getPath2(h->getPosition(false) + dir, path)) + if(!LOCPLINT->cb->getPathsInfo(h)->getPath(h->getPosition(false) + dir, path)) { terrain.currentPath = nullptr; return; @@ -997,7 +997,7 @@ int3 CAdvMapInt::verifyPos(int3 ver) void CAdvMapInt::select(const CArmedInstance *sel, bool centerView /*= true*/) { assert(sel); - LOCPLINT->cb->setSelection(sel); + LOCPLINT->setSelection(sel); selection = sel; if (LOCPLINT->battleInt == nullptr && LOCPLINT->makingTurn) { @@ -1184,7 +1184,7 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos) } else if(const CGHeroInstance * currentHero = curHero()) //hero is selected { - const CGPathNode *pn = LOCPLINT->cb->getPathInfo(mapPos); + const CGPathNode *pn = LOCPLINT->cb->getPathsInfo(currentHero)->getPathInfo(mapPos); if(currentHero == topBlocking) //clicked selected hero { LOCPLINT->openHeroWindow(currentHero); @@ -1206,7 +1206,7 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos) { CGPath &path = LOCPLINT->paths[currentHero]; terrain.currentPath = &path; - bool gotPath = LOCPLINT->cb->getPath2(mapPos, path); //try getting path, erase if failed + bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(mapPos, path); //try getting path, erase if failed updateMoveHero(currentHero); if (!gotPath) LOCPLINT->eraseCurrentPathOf(currentHero); @@ -1249,11 +1249,6 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) statusbar.setText(hlp); } - const CGPathNode *pnode = LOCPLINT->cb->getPathInfo(mapPos); - - int turns = pnode->turns; - vstd::amin(turns, 3); - if(!selection) //may occur just at the start of game (fake move before full intiialization) return; @@ -1298,6 +1293,10 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) } else if(const CGHeroInstance *h = curHero()) { + const CGPathNode *pnode = LOCPLINT->cb->getPathsInfo(h)->getPathInfo(mapPos); + + int turns = pnode->turns; + vstd::amin(turns, 3); bool accessible = pnode->turns < 255; if(objAtTile) diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index 2b4607cf9..4632ea6ed 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -3,6 +3,7 @@ #include "BattleAction.h" #include "IGameEventsReceiver.h" +#include "CGameStateFwd.h" /* * CGameInterface.h, part of VCMI engine @@ -45,6 +46,7 @@ struct StackLocation; class CStackInstance; class CCommanderInstance; class CStack; +class CPathsInfo; class CCreature; class CLoadFile; class CSaveFile; @@ -73,7 +75,7 @@ public: }; /// Central class for managing human player / AI interface logic -class CGameInterface : public CBattleGameInterface, public IGameEventsReceiver +class DLL_LINKAGE CGameInterface : public CBattleGameInterface, public IGameEventsReceiver { public: virtual void init(shared_ptr CB){}; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index e8810e763..3a2f4a7d9 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2165,10 +2165,10 @@ void CGameState::apply(CPack *pack) applierGs->apps[typ]->applyOnGS(this,pack); } -void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src, int movement) +void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out) { CPathfinder pathfinder(out, this, hero); - pathfinder.calculatePaths(src, movement); + pathfinder.calculatePaths(); } /** @@ -2896,9 +2896,29 @@ bool CGPathNode::reachable() const return turns < 255; } -bool CPathsInfo::getPath( const int3 &dst, CGPath &out ) +const CGPathNode * CPathsInfo::getPathInfo( int3 tile ) const { - assert(isValid); + boost::unique_lock pathLock(pathMx); + + if (tile.x >= sizes.x || tile.y >= sizes.y || tile.z >= sizes.z) + return nullptr; + return &nodes[tile.x][tile.y][tile.z]; +} + +int CPathsInfo::getDistance( int3 tile ) const +{ + boost::unique_lock pathLock(pathMx); + + CGPath ret; + if (getPath(tile, ret)) + return ret.nodes.size(); + else + return 255; +} + +bool CPathsInfo::getPath( const int3 &dst, CGPath &out ) const +{ + boost::unique_lock pathLock(pathMx); out.nodes.clear(); const CGPathNode *curnode = &nodes[dst.x][dst.y][dst.z]; @@ -3277,14 +3297,12 @@ void CPathfinder::initializeGraph() } } -void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*= -1*/) +void CPathfinder::calculatePaths() { + int3 src = hero->getPosition(false); 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); @@ -3295,9 +3313,9 @@ void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*= }; out.hero = hero; - out.hpos = src; + out.hpos = hero->getPosition(false); - if(!gs->map->isInTheMap(src)/* || !gs->map->isInTheMap(dest)*/) //check input + if(!gs->map->isInTheMap(out.hpos)/* || !gs->map->isInTheMap(dest)*/) //check input { logGlobal->errorStream() << "CGameState::calculatePaths: Hero outside the gs->map? How dare you..."; return; @@ -3307,9 +3325,9 @@ void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*= //initial tile - set cost on 0 and add to the queue - CGPathNode &initialNode = *getNode(src); + CGPathNode &initialNode = *getNode(out.hpos); initialNode.turns = 0; - initialNode.moveRemains = movement; + initialNode.moveRemains = hero->movement; mq.push_back(&initialNode); std::vector neighbours; @@ -3425,8 +3443,6 @@ void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*= } } //neighbours loop } //queue loop - - out.isValid = true; } CGPathNode *CPathfinder::getNode(const int3 &coord) diff --git a/lib/CGameState.h b/lib/CGameState.h index fb74537ae..34be5eb55 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -216,50 +216,6 @@ struct UpgradeInfo UpgradeInfo(){oldID = CreatureID::NONE;}; }; -struct DLL_LINKAGE CGPathNode -{ - enum EAccessibility - { - NOT_SET = 0, - ACCESSIBLE = 1, //tile can be entered and passed - VISITABLE, //tile can be entered as the last tile in path - BLOCKVIS, //visitable from neighbouring tile but not passable - BLOCKED //tile can't be entered nor visited - }; - - EAccessibility accessible; - ui8 land; - ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn - ui32 moveRemains; //remaining tiles after hero reaches the tile - CGPathNode * theNodeBefore; - int3 coord; //coordinates - - CGPathNode(); - bool reachable() const; -}; - -struct DLL_LINKAGE CGPath -{ - std::vector nodes; //just get node by node - - int3 startPos() const; // start point - int3 endPos() const; //destination point - void convert(ui8 mode); //mode=0 -> from 'manifest' to 'object' -}; - -struct DLL_LINKAGE CPathsInfo -{ - bool isValid; - const CGHeroInstance *hero; - int3 hpos; - int3 sizes; - CGPathNode ***nodes; //[w][h][level] - - bool getPath(const int3 &dst, CGPath &out); - CPathsInfo(const int3 &Sizes); - ~CPathsInfo(); -}; - struct DLL_EXPORT DuelParameters { ETerrainType terType; @@ -349,7 +305,7 @@ private: public: CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero); - void calculatePaths(int3 src = int3(-1,-1,-1), int movement = -1); //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 + void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists }; @@ -397,7 +353,7 @@ public: UpgradeInfo getUpgradeInfo(const CStackInstance &stack); PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2); bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile - void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1); //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 + 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; diff --git a/lib/CGameStateFwd.h b/lib/CGameStateFwd.h index b284373e3..5dc1672b6 100644 --- a/lib/CGameStateFwd.h +++ b/lib/CGameStateFwd.h @@ -118,3 +118,49 @@ struct DLL_LINKAGE QuestInfo //universal interface for human and AI } }; +struct DLL_LINKAGE CGPathNode +{ + enum EAccessibility + { + NOT_SET = 0, + ACCESSIBLE = 1, //tile can be entered and passed + VISITABLE, //tile can be entered as the last tile in path + BLOCKVIS, //visitable from neighbouring tile but not passable + BLOCKED //tile can't be entered nor visited + }; + + EAccessibility accessible; + ui8 land; + ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn + ui32 moveRemains; //remaining tiles after hero reaches the tile + CGPathNode * theNodeBefore; + int3 coord; //coordinates + + CGPathNode(); + bool reachable() const; +}; + +struct DLL_LINKAGE CGPath +{ + std::vector nodes; //just get node by node + + int3 startPos() const; // start point + int3 endPos() const; //destination point + void convert(ui8 mode); //mode=0 -> from 'manifest' to 'object' +}; + +struct DLL_LINKAGE CPathsInfo +{ + mutable boost::mutex pathMx; + + const CGHeroInstance *hero; + int3 hpos; + int3 sizes; + CGPathNode ***nodes; //[w][h][level] + + const CGPathNode * getPathInfo( int3 tile ) const; + bool getPath(const int3 &dst, CGPath &out) const; + int getDistance( int3 tile ) const; + CPathsInfo(const int3 &Sizes); + ~CPathsInfo(); +}; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 68adc894f..803ff033d 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3779,13 +3779,13 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) } void CGameHandler::playerMessage( PlayerColor player, const std::string &message ) -{/* +{ bool cheated=true; PlayerMessage temp_message(player, message); sendAndApply(&temp_message); if(message == "vcmiistari") //give all spells and 999 mana - { + {/* SetMana sm; ChangeSpells cs; @@ -3866,6 +3866,18 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message if(!hero->getArt(ArtifactPosition::MACH3)) giveHeroNewArtifact(hero, VLC->arth->artifacts.at(6), ArtifactPosition::MACH3); } + else if (message == "vcmiforgeofnoldorking") //hero gets all artifacts except war machines, spell scrolls and spell book + { + CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection); + if(!hero) return; + for (int g = 7; g < VLC->arth->artifacts.size(); ++g) //including artifacts from mods + giveHeroNewArtifact(hero, VLC->arth->artifacts.at(g), ArtifactPosition::PRE_FIRST); + } + else if(message == "vcmiglorfindel") //selected hero gains a new level + { + CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection); + changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level+1) - VLC->heroh->reqExp(hero->level)); + } else if(message == "vcminahar") //1000000 movement points { CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection); @@ -3873,7 +3885,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message SetMovePoints smp; smp.hid = hero->id; smp.val = 1000000; - sendAndApply(&smp); + sendAndApply(&smp);*/ } else if(message == "vcmiformenos") //give resources { @@ -3901,11 +3913,6 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message delete [] hlp_tab; sendAndApply(&fc); } - else if(message == "vcmiglorfindel") //selected hero gains a new level - { - CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection); - changePrimSkill(hero, PrimarySkill::EXPERIENCE, VLC->heroh->reqExp(hero->level+1) - VLC->heroh->reqExp(hero->level)); - } else if(message == "vcmisilmaril") //player wins { gs->getPlayer(player)->enteredWinningCheatCode = 1; @@ -3914,13 +3921,6 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message { gs->getPlayer(player)->enteredLosingCheatCode = 1; } - else if (message == "vcmiforgeofnoldorking") //hero gets all artifacts except war machines, spell scrolls and spell book - { - CGHeroInstance *hero = gs->getHero(gs->getPlayer(player)->currentSelection); - if(!hero) return; - for (int g = 7; g < VLC->arth->artifacts.size(); ++g) //including artifacts from mods - giveHeroNewArtifact(hero, VLC->arth->artifacts.at(g), ArtifactPosition::PRE_FIRST); - } else cheated = false; if(cheated) @@ -3928,7 +3928,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message SystemMessage temp_message(VLC->generaltexth->allTexts.at(260)); sendAndApply(&temp_message); checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature - }*/ + } } void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,