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

Refactored access to hero paths in CPlayerInterface

This commit is contained in:
Ivan Savenko 2023-02-15 12:08:32 +02:00
parent bb6e1f7ee1
commit 600054e001
7 changed files with 241 additions and 141 deletions

View File

@ -120,7 +120,164 @@ struct HeroObjectRetriever : boost::static_visitor<const CGHeroInstance *>
}
};
CPlayerInterface::CPlayerInterface(PlayerColor Player)
HeroPathStorage::HeroPathStorage(CPlayerInterface & owner):
owner(owner)
{
}
void HeroPathStorage::setPath(const CGHeroInstance *h, const CGPath & path)
{
paths[h] = path;
}
const CGPath & HeroPathStorage::getPath(const CGHeroInstance *h) const
{
assert(hasPath(h));
return paths.at(h);
}
bool HeroPathStorage::hasPath(const CGHeroInstance *h) const
{
return paths.count(h) > 0;
}
bool HeroPathStorage::setPath(const CGHeroInstance *h, const int3 & destination)
{
CGPath path;
if (!owner.cb->getPathsInfo(h)->getPath(path, destination))
return false;
setPath(h, path);
return true;
}
void HeroPathStorage::removeLastNode(const CGHeroInstance *h)
{
assert(hasPath(h));
if (!hasPath(h))
return;
auto & path = paths[h];
path.nodes.pop_back();
if (path.nodes.size() < 2) //if it was the last one, remove entire path and path with only one tile is not a real path
erasePath(h);
}
void HeroPathStorage::erasePath(const CGHeroInstance *h)
{
paths.erase(h);
adventureInt->updateMoveHero(h, false);
}
void HeroPathStorage::verifyPath(const CGHeroInstance *h)
{
if (!hasPath(h))
return;
setPath(h, getPath(h).endPos());
}
//CGPath * CPlayerInterface::getAndVerifyPath(const CGHeroInstance * h)
//{
// if (vstd::contains(paths,h)) //hero has assigned path
// {
// CGPath &path = paths[h];
// if (!path.nodes.size())
// {
// logGlobal->warn("Warning: empty path found...");
// paths.erasePath(h);
// }
// else
// {
// assert(h->visitablePos() == path.startPos());
// //update the hero path in case of something has changed on map
// if (LOCPLINT->cb->getPathsInfo(h)->getPath(path, path.endPos()))
// return &path;
//
// paths.erase(h);
// return nullptr;
// }
// }
//
// return nullptr;
//}
//
//CGPath * CPlayerInterface::getPath(const CGHeroInstance * h)
//{
// if (vstd::contains(paths,h)) //hero has assigned path
// return &paths[h];
//
// return nullptr;
//}
//removeLastNode
//void HeroPathStorage::setPath(const CGHeroInstance *h, const CGPath & path)
//{
//
//}
//
//const CGPath & HeroPathStorage::getPath(const CGHeroInstance *h)
//{
//
//}
//
//void HeroPathStorage::verifyPath(const CGHeroInstance *h)
//{
//
//}
//void CPlayerInterface::eraseCurrentPathOf(const CGHeroInstance * ho, bool checkForExistanceOfPath)
//{
// if (checkForExistanceOfPath)
// {
// assert(vstd::contains(paths, ho));
// }
// else if (!vstd::contains(paths, ho))
// {
// return;
// }
// assert(ho == adventureInt->selection);
//
// paths.erasePath(ho);
// adventureInt->updateMoveHero(ho, false);
//}
template<typename Handler>
void HeroPathStorage::serialize(Handler & h, int version)
{
std::map<const CGHeroInstance *, int3> pathsMap; //hero -> dest
if (h.saving)
{
for (auto &p : paths)
{
if (p.second.nodes.size())
pathsMap[p.first] = p.second.endPos();
else
logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->getNameTranslated());
}
h & pathsMap;
}
else
{
h & pathsMap;
if (owner.cb)
{
for (auto &p : pathsMap)
{
CGPath path;
owner.cb->getPathsInfo(p.first)->getPath(path, p.second);
paths[p.first] = path;
logGlobal->trace("Restored path for hero %s leading to %s with %d nodes", p.first->nodeName(), p.second.toString(), path.nodes.size());
}
}
}
}
CPlayerInterface::CPlayerInterface(PlayerColor Player):
paths(*this)
{
logGlobal->trace("\tHuman player interface for player %s being constructed", Player.getStr());
destinationTeleport = ObjectInstanceID();
@ -260,8 +417,8 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
bool directlyAttackingCreature =
details.attackedFrom
&& adventureInt->terrain->currentPath //in case if movement has been canceled in the meantime and path was already erased
&& adventureInt->terrain->currentPath->nodes.size() == 3;//FIXME should be 2 but works nevertheless...
&& paths.hasPath(hero) //in case if movement has been canceled in the meantime and path was already erased
&& paths.getPath(hero).nodes.size() == 3;//FIXME should be 2 but works nevertheless...
if(makingTurn && hero->tempOwner == playerID) //we are moving our hero - we may need to update assigned path
{
@ -271,21 +428,21 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
if(details.result == TryMoveHero::TELEPORTATION)
{
if(adventureInt->terrain->currentPath)
if(paths.hasPath(hero))
{
assert(adventureInt->terrain->currentPath->nodes.size() >= 2);
std::vector<CGPathNode>::const_iterator nodesIt = adventureInt->terrain->currentPath->nodes.end() - 1;
assert(paths.getPath(hero).nodes.size() >= 2);
auto nodesIt = paths.getPath(hero).nodes.end() - 1;
if((nodesIt)->coord == hero->convertToVisitablePos(details.start)
&& (nodesIt - 1)->coord == hero->convertToVisitablePos(details.end))
{
//path was between entrance and exit of teleport -> OK, erase node as usual
removeLastNodeFromPath(hero);
paths.removeLastNode(hero);
}
else
{
//teleport was not along current path, it'll now be invalid (hero is somewhere else)
eraseCurrentPathOf(hero);
paths.erasePath(hero);
}
}
@ -299,12 +456,12 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
if(hero->pos != details.end //hero didn't change tile but visit succeeded
|| directlyAttackingCreature) // or creature was attacked from endangering tile.
{
eraseCurrentPathOf(hero, false);
paths.erasePath(hero);
}
else if(adventureInt->terrain->currentPath && hero->pos == details.end) //&& hero is moving
else if(paths.hasPath(hero) && hero->pos == details.end) //&& hero is moving
{
if(details.start != details.end) //so we don't touch path when revisiting with spacebar
removeLastNodeFromPath(hero);
paths.removeLastNode(hero);
}
}
@ -444,8 +601,7 @@ void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
else if (adventureInt->selection == hero)
adventureInt->selection = nullptr;
if (vstd::contains(paths, hero))
paths.erase(hero);
paths.erasePath(hero);
}
void CPlayerInterface::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start)
@ -1247,7 +1403,7 @@ void CPlayerInterface::heroBonusChanged( const CGHeroInstance *hero, const Bonus
if ((bonus.type == Bonus::FLYING_MOVEMENT || bonus.type == Bonus::WATER_WALKING) && !gain)
{
//recalculate paths because hero has lost bonus influencing pathfinding
eraseCurrentPathOf(hero, false);
paths.erasePath(hero);
}
}
@ -1256,33 +1412,7 @@ template <typename Handler> void CPlayerInterface::serializeTempl( Handler &h, c
h & wanderingHeroes;
h & towns;
h & sleepingHeroes;
std::map<const CGHeroInstance *, int3> pathsMap; //hero -> dest
if (h.saving)
{
for (auto &p : paths)
{
if (p.second.nodes.size())
pathsMap[p.first] = p.second.endPos();
else
logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->getNameTranslated());
}
h & pathsMap;
}
else
{
h & pathsMap;
if (cb)
for (auto &p : pathsMap)
{
CGPath path;
cb->getPathsInfo(p.first)->getPath(path, p.second);
paths[p.first] = path;
logGlobal->trace("Restored path for hero %s leading to %s with %d nodes", p.first->nodeName(), p.second.toString(), path.nodes.size());
}
}
h & paths;
h & spellbookSettings;
}
@ -1299,7 +1429,7 @@ void CPlayerInterface::loadGame( BinaryDeserializer & h, const int version )
firstCall = -1;
}
void CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
void CPlayerInterface::moveHero( const CGHeroInstance *h, const CGPath& path )
{
LOG_TRACE(logGlobal);
if (!LOCPLINT->makingTurn)
@ -1330,7 +1460,7 @@ void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHer
EVENT_HANDLER_CALLED_BY_CLIENT;
auto onEnd = [=](){ cb->selectionMade(0, queryID); };
if (stillMoveHero.get() == DURING_MOVE && adventureInt->terrain->currentPath && adventureInt->terrain->currentPath->nodes.size() > 1) //to ignore calls on passing through garrisons
if (stillMoveHero.get() == DURING_MOVE && paths.hasPath(down) && paths.getPath(down).nodes.size() > 1) //to ignore calls on passing through garrisons
{
onEnd();
return;
@ -1641,7 +1771,7 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns
bool heroVisibleHere = false;
auto & tile = subArr[tileX][tileY];
for ( auto const & obj : tile.objects)
for(const auto & obj : tile.objects)
{
if (obj.obj == ho)
{
@ -1856,7 +1986,7 @@ void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellI
GH.popInts(1);
if(spellID == SpellID::FLY || spellID == SpellID::WATER_WALK)
eraseCurrentPathOf(caster, false);
paths.erasePath(caster);
const spells::Spell * spell = CGI->spells()->getByIndex(spellID);
@ -1872,54 +2002,6 @@ void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellI
CCS->soundh->playSound(castSoundPath);
}
void CPlayerInterface::eraseCurrentPathOf(const CGHeroInstance * ho, bool checkForExistanceOfPath)
{
if (checkForExistanceOfPath)
{
assert(vstd::contains(paths, ho));
}
else if (!vstd::contains(paths, ho))
{
return;
}
assert(ho == adventureInt->selection);
paths.erase(ho);
adventureInt->terrain->currentPath = nullptr;
adventureInt->updateMoveHero(ho, false);
}
void CPlayerInterface::removeLastNodeFromPath(const CGHeroInstance *ho)
{
adventureInt->terrain->currentPath->nodes.erase(adventureInt->terrain->currentPath->nodes.end()-1);
if (adventureInt->terrain->currentPath->nodes.size() < 2) //if it was the last one, remove entire path and path with only one tile is not a real path
eraseCurrentPathOf(ho);
}
CGPath * CPlayerInterface::getAndVerifyPath(const CGHeroInstance * h)
{
if (vstd::contains(paths,h)) //hero has assigned path
{
CGPath &path = paths[h];
if (!path.nodes.size())
{
logGlobal->warn("Warning: empty path found...");
paths.erase(h);
}
else
{
assert(h->visitablePos() == path.startPos());
//update the hero path in case of something has changed on map
if (LOCPLINT->cb->getPathsInfo(h)->getPath(path, path.endPos()))
return &path;
else
paths.erase(h);
}
}
return nullptr;
}
void CPlayerInterface::acceptTurn()
{
if (settings["session"]["autoSkip"].Bool())

View File

@ -64,12 +64,39 @@ namespace boost
class recursive_mutex;
}
class CPlayerInterface;
class HeroPathStorage
{
CPlayerInterface & owner;
std::map<const CGHeroInstance *, CGPath> paths; //maps hero => selected path in adventure map
public:
explicit HeroPathStorage(CPlayerInterface &owner);
void setPath(const CGHeroInstance *h, const CGPath & path);
bool setPath(const CGHeroInstance *h, const int3 & destination);
const CGPath & getPath(const CGHeroInstance *h) const;
bool hasPath(const CGHeroInstance *h) const;
void removeLastNode(const CGHeroInstance *h);
void erasePath(const CGHeroInstance *h);
void verifyPath(const CGHeroInstance *h);
template <typename Handler>
void serialize( Handler &h, int version );
};
/// Central class for managing user interface logic
class CPlayerInterface : public CGameInterface, public IUpdateable
{
const CArmedInstance * currentSelection;
public:
HeroPathStorage paths;
std::shared_ptr<Environment> env;
ObjectInstanceID destinationTeleport; //contain -1 or object id if teleportation
int3 destinationTeleportPos;
@ -94,7 +121,6 @@ public:
std::vector<const CGHeroInstance *> wanderingHeroes; //our heroes on the adventure map (not the garrisoned ones)
std::vector<const CGTownInstance *> towns; //our towns on the adventure map
std::map<const CGHeroInstance *, CGPath> paths; //maps hero => selected path in adventure map
std::vector<const CGHeroInstance *> sleepingHeroes; //if hero is in here, he's sleeping
//During battle is quick combat mode is used
@ -229,14 +255,11 @@ public:
void showYesNoDialog(const std::string &text, CFunctionList<void()> onYes, CFunctionList<void()> onNo, const std::vector<std::shared_ptr<CComponent>> & components = std::vector<std::shared_ptr<CComponent>>());
void stopMovement();
void moveHero(const CGHeroInstance *h, CGPath path);
void moveHero(const CGHeroInstance *h, const CGPath& path);
void initMovement(const TryMoveHero &details, const CGHeroInstance * ho, const int3 &hp );//initializing objects and performing first step of move
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 removeLastNodeFromPath(const CGHeroInstance *ho);
CGPath *getAndVerifyPath( const CGHeroInstance * h );
void acceptTurn(); //used during hot seat after your turn message is close
void tryDiggging(const CGHeroInstance *h);
void showShipyardDialogOrProblemPopup(const IShipyard *obj); //obj may be town or shipyard;

View File

@ -324,10 +324,10 @@ void CAdvMapInt::fsleepWake()
void CAdvMapInt::fmoveHero()
{
const CGHeroInstance *h = curHero();
if (!h || !terrain->currentPath || !CGI->mh->canStartHeroMovement())
if (!h || !LOCPLINT->paths.hasPath(h) || !CGI->mh->canStartHeroMovement())
return;
LOCPLINT->moveHero(h, *terrain->currentPath);
LOCPLINT->moveHero(h, LOCPLINT->paths.getPath(h));
}
void CAdvMapInt::fshowSpellbok()
@ -373,10 +373,18 @@ void CAdvMapInt::fendTurn()
// Only show hero reminder if conditions met:
// - There still movement points
// - Hero don't have a path or there not points for first step on path
auto path = LOCPLINT->getAndVerifyPath(hero);
if(!path || path->nodes.size() < 2 || !path->nodes[path->nodes.size()-2].turns)
LOCPLINT->paths.verifyPath(hero);
if(!LOCPLINT->paths.hasPath(hero))
{
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[55], std::bind(&CAdvMapInt::endingTurn, this), nullptr);
LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], std::bind(&CAdvMapInt::endingTurn, this), nullptr );
return;
}
auto path = LOCPLINT->paths.getPath(hero);
if (path.nodes.size() < 2 || path.nodes[path.nodes.size() - 2].turns)
{
LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], std::bind(&CAdvMapInt::endingTurn, this), nullptr );
return;
}
}
@ -405,7 +413,7 @@ void CAdvMapInt::updateMoveHero(const CGHeroInstance *h, tribool hasPath)
}
//default value is for everywhere but CPlayerInterface::moveHero, because paths are not updated from there immediately
if(boost::logic::indeterminate(hasPath))
hasPath = LOCPLINT->paths[h].nodes.size() ? true : false;
hasPath = LOCPLINT->paths.hasPath(h);
moveHero->block(!(bool)hasPath || (h->movement == 0));
}
@ -865,14 +873,15 @@ void CAdvMapInt::keyPressed(const SDL_Keycode & key)
return;
}
CGPath &path = LOCPLINT->paths[h];
terrain->currentPath = &path;
int3 dst = h->visitablePos() + int3(direction->x, direction->y, 0);
if(dst != verifyPos(dst) || !LOCPLINT->cb->getPathsInfo(h)->getPath(path, dst))
{
terrain->currentPath = nullptr;
if(dst != verifyPos(dst))
return;
}
if ( !LOCPLINT->paths.setPath(h, dst))
return;
const CGPath & path = LOCPLINT->paths.getPath(h);
if (path.nodes.size() > 2)
updateMoveHero(h);
@ -938,7 +947,6 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView)
if(centerView)
centerOn(sel);
terrain->currentPath = nullptr;
if(sel->ID==Obj::TOWN)
{
auto town = dynamic_cast<const CGTownInstance*>(sel);
@ -959,8 +967,6 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView)
heroList->select(hero);
townList->select(nullptr);
terrain->currentPath = LOCPLINT->getAndVerifyPath(hero);
updateSleepWake(hero);
updateMoveHero(hero);
updateSpellbook(hero);
@ -1155,7 +1161,6 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
bool isHero = false;
if(selection->ID != Obj::HERO) //hero is not selected (presumably town)
{
assert(!terrain->currentPath); //path can be active only when hero is selected
if(selection == topBlocking) //selected town clicked
LOCPLINT->openTownWindow(static_cast<const CGTownInstance*>(topBlocking));
else if(canSelect)
@ -1178,25 +1183,16 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
}
else //still here? we need to move hero if we clicked end of already selected path or calculate a new path otherwise
{
if(terrain->currentPath && terrain->currentPath->endPos() == mapPos)//we'll be moving
if(LOCPLINT->paths.hasPath(currentHero) &&
LOCPLINT->paths.getPath(currentHero).endPos() == mapPos)//we'll be moving
{
if(CGI->mh->canStartHeroMovement())
LOCPLINT->moveHero(currentHero, *terrain->currentPath);
LOCPLINT->moveHero(currentHero, LOCPLINT->paths.getPath(currentHero));
return;
}
else //remove old path and find a new one if we clicked on accessible tile
{
CGPath &path = LOCPLINT->paths[currentHero];
CGPath newpath;
bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(newpath, mapPos); //try getting path, erase if failed
if(gotPath && newpath.nodes.size())
path = newpath;
if(path.nodes.size())
terrain->currentPath = &path;
else
LOCPLINT->eraseCurrentPathOf(currentHero);
LOCPLINT->paths.setPath(currentHero, mapPos);
updateMoveHero(currentHero);
}
}

View File

@ -35,11 +35,11 @@
#define ADVOPT (conf.go()->ac)
CTerrainRect::CTerrainRect()
: fadeSurface(nullptr),
lastRedrawStatus(EMapAnimRedrawStatus::OK),
fadeAnim(std::make_shared<CFadeAnimation>()),
curHoveredTile(-1,-1,-1),
currentPath(nullptr)
: fadeSurface(nullptr)
, lastRedrawStatus(EMapAnimRedrawStatus::OK)
, fadeAnim(std::make_shared<CFadeAnimation>())
, curHoveredTile(-1, -1, -1)
, isSwiping(false)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
@ -90,13 +90,12 @@ void CTerrainRect::clickLeft(tribool down, bool previousState)
}
}
else
{
#endif
{
if(down == false)
return;
#if defined(VCMI_MOBILE)
}
#endif
int3 mp = whichTileIsIt();
if(mp.x < 0 || mp.y < 0 || mp.x >= LOCPLINT->cb->getMapSize().x || mp.y >= LOCPLINT->cb->getMapSize().y)
return;
@ -146,8 +145,8 @@ void CTerrainRect::handleSwipeMove(const Point & cursorPosition)
if(!isSwiping)
{
// try to distinguish if this touch was meant to be a swipe or just fat-fingering press
if(abs(cursorPosition.x - swipeInitialRealPos.x) > SwipeTouchSlop ||
abs(cursorPosition.y - swipeInitialRealPos.y) > SwipeTouchSlop)
if(std::abs(cursorPosition.x - swipeInitialRealPos.x) > SwipeTouchSlop ||
std::abs(cursorPosition.y - swipeInitialRealPos.y) > SwipeTouchSlop)
{
isSwiping = true;
}
@ -169,7 +168,8 @@ bool CTerrainRect::handleSwipeStateChange(bool btnPressed)
swipeInitialViewPos = getViewCenter();
return true;
}
else if(isSwiping) // only accept this touch if it wasn't a swipe
if(isSwiping) // only accept this touch if it wasn't a swipe
{
isSwiping = false;
return true;

View File

@ -51,7 +51,7 @@ class CTerrainRect : public CIntObject
bool needsAnimUpdate();
public:
CGPath * currentPath;
//CGPath * currentPath;
CTerrainRect();
~CTerrainRect();

View File

@ -365,7 +365,6 @@ private:
int offsetY;
//terrain graphics
private:
//FIXME: unique_ptr should be enough, but fails to compile in MSVS 2013
typedef std::map<std::string, std::array<std::shared_ptr<CAnimation>, 4>> TFlippedAnimations; //[type, rotation]
typedef std::map<std::string, std::vector<std::array<std::shared_ptr<IImage>, 4>>> TFlippedCache;//[type, view type, rotation]

View File

@ -1219,7 +1219,7 @@ void CCastleInterface::castleTeleport(int where)
const CGTownInstance * dest = LOCPLINT->cb->getTown(ObjectInstanceID(where));
adventureInt->select(town->visitingHero);//according to assert(ho == adventureInt->selection) in the eraseCurrentPathOf
LOCPLINT->cb->teleportHero(town->visitingHero, dest);
LOCPLINT->eraseCurrentPathOf(town->visitingHero, false);
LOCPLINT->paths.erasePath(town->visitingHero);
}
void CCastleInterface::townChange()