diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 4d85fd0b7..a8cf93959 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -89,9 +89,9 @@ CondSh stillMoveHero; //used during hero movement int CPlayerInterface::howManyPeople = 0; -static bool objectBlitOrderSorter(const std::pair & a, const std::pair & b) +static bool objectBlitOrderSorter(const TerrainTileObject & a, const TerrainTileObject & b) { - return CMapHandler::compareObjectBlitOrder(a.first, b.first); + return CMapHandler::compareObjectBlitOrder(a.obj, b.obj); } CPlayerInterface::CPlayerInterface(PlayerColor Player) @@ -205,9 +205,9 @@ STRONG_INLINE void subRect(const int & x, const int & y, const int & z, const SD { TerrainTile2 & hlp = CGI->mh->ttiles[x][y][z]; for(auto & elem : hlp.objects) - if(elem.first->id == hid) + if(elem.obj->id == hid) { - elem.second = r; + elem.rect = r; return; } } @@ -216,7 +216,7 @@ STRONG_INLINE void delObjRect(const int & x, const int & y, const int & z, const { TerrainTile2 & hlp = CGI->mh->ttiles[x][y][z]; for(int h=0; hid == hid) + if(hlp.objects[h].obj->id == hid) { hlp.objects.erase(hlp.objects.begin()+h); return; @@ -239,8 +239,8 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) // TODO -> we should not need full CGHeroInstance structure to display animation or it should not be handled by playerint (but by the client itself) const TerrainTile2 &tile = CGI->mh->ttiles[hp.x-1][hp.y][hp.z]; for(auto & elem : tile.objects) - if(elem.first->id == details.id) - hero = dynamic_cast(elem.first); + if(elem.obj->id == details.id) + hero = dynamic_cast(elem.obj); if(!hero) //still nothing... return; @@ -276,7 +276,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) } } - adventureInt->centerOn(hero); //actualizing screen pos + adventureInt->centerOn(hero, true); //actualizing screen pos adventureInt->minimap.redraw(); adventureInt->heroList.update(hero); return; //teleport - no fancy moving animation @@ -1676,17 +1676,17 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns { //ho->moveDir = 1; ho->isStanding = false; - CGI->mh->ttiles[hp.x-3][hp.y-2][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, -31, -31))); - CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 1, -31))); - CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 33, -31))); - CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 65, -31))); + CGI->mh->ttiles[hp.x-3][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, -31))); + CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 1, -31))); + CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 33, -31))); + CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 65, -31))); - CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, -31, 1))); + CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 1))); subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1, 1), ho->id); subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33, 1), ho->id); subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65, 1), ho->id); - CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, -31, 33))); + CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 33))); subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1, 33), ho->id); subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33, 33), ho->id); subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65, 33), ho->id); @@ -1704,9 +1704,9 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns { //ho->moveDir = 2; ho->isStanding = false; - CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 0, -31))); - CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 32, -31))); - CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 64, -31))); + CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 0, -31))); + CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 32, -31))); + CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 64, -31))); subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 0, 1), ho->id); subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 32, 1), ho->id); @@ -1724,20 +1724,20 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns { //ho->moveDir = 3; ho->isStanding = false; - CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, -1, -31))); - CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 31, -31))); - CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 63, -31))); - CGI->mh->ttiles[hp.x+1][hp.y-2][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 95, -31))); + CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -1, -31))); + CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 31, -31))); + CGI->mh->ttiles[hp.x][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 63, -31))); + CGI->mh->ttiles[hp.x+1][hp.y-2][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, -31))); subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1, 1), ho->id); subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31, 1), ho->id); subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63, 1), ho->id); - CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 95, 1))); + CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 1))); subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1, 33), ho->id); subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31, 33), ho->id); subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63, 33), ho->id); - CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 95, 33))); + CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 33))); std::stable_sort(CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-2][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter); std::stable_sort(CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-1][hp.y-2][hp.z].objects.end(), objectBlitOrderSorter); @@ -1755,12 +1755,12 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1, 0), ho->id); subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31, 0), ho->id); subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63, 0), ho->id); - CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 95, 0))); + CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 0))); subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1, 32), ho->id); subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31, 32), ho->id); subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63, 32), ho->id); - CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 95, 32))); + CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 32))); std::stable_sort(CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.end(), objectBlitOrderSorter); @@ -1773,17 +1773,17 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1, -1), ho->id); subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31, -1), ho->id); subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63, -1), ho->id); - CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 95, -1))); + CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, -1))); subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1, 31), ho->id); subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31, 31), ho->id); subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63, 31), ho->id); - CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 95, 31))); + CGI->mh->ttiles[hp.x+1][hp.y][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 31))); - CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, -1, 63))); - CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 31, 63))); - CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 63, 63))); - CGI->mh->ttiles[hp.x+1][hp.y+1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 95, 63))); + CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -1, 63))); + CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 31, 63))); + CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 63, 63))); + CGI->mh->ttiles[hp.x+1][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 63))); std::stable_sort(CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x+1][hp.y-1][hp.z].objects.end(), objectBlitOrderSorter); @@ -1806,9 +1806,9 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 32, 31), ho->id); subRect(hp.x, hp.y, hp.z, genRect(32, 32, 64, 31), ho->id); - CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 0, 63))); - CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 32, 63))); - CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 64, 63))); + CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 0, 63))); + CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 32, 63))); + CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 64, 63))); std::stable_sort(CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter); std::stable_sort(CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.end(), objectBlitOrderSorter); @@ -1818,20 +1818,20 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns { //ho->moveDir = 7; ho->isStanding = false; - CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, -31, -1))); + CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, -1))); subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1, -1), ho->id); subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33, -1), ho->id); subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65, -1), ho->id); - CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, -31, 31))); + CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 31))); subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1, 31), ho->id); subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33, 31), ho->id); subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65, 31), ho->id); - CGI->mh->ttiles[hp.x-3][hp.y+1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, -31, 63))); - CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 1, 63))); - CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 33, 63))); - CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, 65, 63))); + CGI->mh->ttiles[hp.x-3][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 63))); + CGI->mh->ttiles[hp.x-2][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 1, 63))); + CGI->mh->ttiles[hp.x-1][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 33, 63))); + CGI->mh->ttiles[hp.x][hp.y+1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 65, 63))); std::stable_sort(CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.begin(), CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.end(), objectBlitOrderSorter); @@ -1846,12 +1846,12 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns { //ho->moveDir = 8; ho->isStanding = false; - CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, -31, 0))); + CGI->mh->ttiles[hp.x-3][hp.y-1][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 0))); subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1, 0), ho->id); subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33, 0), ho->id); subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65, 0), ho->id); - CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.push_back(std::make_pair(ho, genRect(32, 32, -31, 32))); + CGI->mh->ttiles[hp.x-3][hp.y][hp.z].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 32))); subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1, 32), ho->id); subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33, 32), ho->id); subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65, 32), ho->id); diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index e3a6639cd..1dee3e8d4 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -336,7 +336,7 @@ void RemoveObject::applyFirstCl( CClient *cl ) { const CGObjectInstance *o = cl->getObj(id); - CGI->mh->hideObject(o); + CGI->mh->hideObject(o, true); //notify interfaces about removal for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++) @@ -367,7 +367,7 @@ void TryMoveHero::applyFirstCl( CClient *cl ) } if(result == TELEPORTATION || result == EMBARK || result == DISEMBARK || !humanKnows) - CGI->mh->removeObject(h); + CGI->mh->removeObject(h, result == EMBARK && humanKnows); if(result == DISEMBARK) @@ -381,7 +381,7 @@ void TryMoveHero::applyCl( CClient *cl ) if(result == TELEPORTATION || result == EMBARK || result == DISEMBARK) { - CGI->mh->printObject(h); + CGI->mh->printObject(h, result == DISEMBARK); } if(result == EMBARK) @@ -908,7 +908,7 @@ void NewObject::applyCl(CClient *cl) cl->invalidatePaths(); const CGObjectInstance *obj = cl->getObj(id); - CGI->mh->printObject(obj); + CGI->mh->printObject(obj, true); for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++) { diff --git a/client/gui/CAnimation.cpp b/client/gui/CAnimation.cpp index 0ff4ca00b..6ecebe647 100644 --- a/client/gui/CAnimation.cpp +++ b/client/gui/CAnimation.cpp @@ -1222,3 +1222,91 @@ void CAnimation::getAnimInfo() logGlobal->errorStream()<<", "<images.begin()->second.size()<<" image loaded in group "<< anim->images.begin()->first; } } + + +float CFadeAnimation::initialCounter() const +{ + if (fadingMode == EMode::OUT) + return 1.0f; + return 0.0f; +} + +void CFadeAnimation::update() +{ + if (!fading) + return; + + if (fadingMode == EMode::OUT) + fadingCounter -= delta; + else + fadingCounter += delta; + + if (isFinished()) + { + fading = false; + if (shouldFreeSurface) + { + SDL_FreeSurface(fadingSurface); + fadingSurface = nullptr; + } + } +} + +bool CFadeAnimation::isFinished() const +{ + if (fadingMode == EMode::OUT) + return fadingCounter <= 0.0f; + return fadingCounter >= 1.0f; +} + +CFadeAnimation::CFadeAnimation() + : fadingSurface(nullptr), + fading(false), + fadingMode(EMode::NONE) +{ +} + +CFadeAnimation::~CFadeAnimation() +{ + if (fadingSurface && shouldFreeSurface) + SDL_FreeSurface(fadingSurface); +} + +void CFadeAnimation::init(EMode mode, SDL_Surface * sourceSurface, bool freeSurfaceAtEnd /* = false */, float animDelta /* = DEFAULT_DELTA */) +{ + if (fading) + { + // in that case, immediately finish the previous fade + // (alternatively, we could just return here to ignore the new fade request until this one finished (but we'd need to free the passed bitmap to avoid leaks)) + logGlobal->warnStream() << "Tried to init fading animation that is already running."; + if (fadingSurface && shouldFreeSurface) + SDL_FreeSurface(fadingSurface); + } + if (animDelta <= 0.0f) + { + logGlobal->warnStream() << "Fade anim: delta should be positive; " << animDelta << " given."; + animDelta = DEFAULT_DELTA; + } + + if (sourceSurface) + fadingSurface = sourceSurface; + + delta = animDelta; + fadingMode = mode; + fadingCounter = initialCounter(); + fading = true; + shouldFreeSurface = freeSurfaceAtEnd; +} + +void CFadeAnimation::draw(SDL_Surface * targetSurface, const SDL_Rect * sourceRect, SDL_Rect * destRect) +{ + if (!fading || !fadingSurface || fadingMode == EMode::NONE) + { + fading = false; + return; + } + + SDL_SetSurfaceAlphaMod(fadingSurface, fadingCounter * 255); + SDL_BlitSurface(fadingSurface, sourceRect, targetSurface, destRect); + SDL_SetSurfaceAlphaMod(fadingSurface, 255); +} diff --git a/client/gui/CAnimation.h b/client/gui/CAnimation.h index 8f2d4b092..fff1690da 100644 --- a/client/gui/CAnimation.h +++ b/client/gui/CAnimation.h @@ -220,3 +220,31 @@ public: //total count of frames in group (including not loaded) size_t size(size_t group=0) const; }; + +class CFadeAnimation +{ +public: + enum class EMode + { + NONE, IN, OUT + }; +private: + static constexpr float DEFAULT_DELTA = 0.05f; + float delta; + SDL_Surface * fadingSurface; + bool fading; + float fadingCounter; + bool shouldFreeSurface; + + float initialCounter() const; + bool isFinished() const; +public: + EMode fadingMode; + + CFadeAnimation(); + ~CFadeAnimation(); + void init(EMode mode, SDL_Surface * sourceSurface, bool freeSurfaceAtEnd = false, float animDelta = DEFAULT_DELTA); + void update(); + void draw(SDL_Surface * targetSurface, const SDL_Rect * sourceRect, SDL_Rect * destRect); + bool isFading() const { return fading; } +}; diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 431398c24..67469bfd4 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -12,6 +12,7 @@ #include "mapHandler.h" #include "CBitmapHandler.h" +#include "gui/CAnimation.h" #include "gui/SDL_Extensions.h" #include "CGameInfo.h" #include "../lib/mapObjects/CGHeroInstance.h" @@ -75,9 +76,9 @@ std::string nameFromType (int typ) return std::string(); } -static bool objectBlitOrderSorter(const std::pair & a, const std::pair & b) +static bool objectBlitOrderSorter(const TerrainTileObject & a, const TerrainTileObject & b) { - return CMapHandler::compareObjectBlitOrder(a.first, b.first); + return CMapHandler::compareObjectBlitOrder(a.obj, b.obj); } struct NeighborTilesInfo @@ -185,10 +186,12 @@ void CMapHandler::prepareFOWDefs() } } -void CMapHandler::drawTerrainRectNew(SDL_Surface * targetSurface, const MapDrawingInfo * info) +EMapAnimRedrawStatus CMapHandler::drawTerrainRectNew(SDL_Surface * targetSurface, const MapDrawingInfo * info, bool redrawOnlyAnim /* = false */) { assert(info); + bool hasActiveFade = updateObjectsFade(); resolveBlitter(info)->blit(targetSurface, info); + return hasActiveFade ? EMapAnimRedrawStatus::REDRAW_REQUESTED : EMapAnimRedrawStatus::OK; } void CMapHandler::roadsRiverTerrainInit() @@ -346,7 +349,7 @@ void CMapHandler::initObjectRects() cr.h = 32; cr.x = bitmap->w - fx * 32 - 32; cr.y = bitmap->h - fy * 32 - 32; - std::pair toAdd = std::make_pair(obj,cr); + TerrainTileObject toAdd(obj,cr); if( map->isInTheMap(currTile) && // within map @@ -609,12 +612,12 @@ void CMapHandler::CMapWorldViewBlitter::drawTileOverlay(SDL_Surface * targetSurf auto & objects = tile.objects; for(auto & object : objects) { - const CGObjectInstance * obj = object.first; + const CGObjectInstance * obj = object.obj; if (obj->pos.z != pos.z) continue; if (!(*info->visibilityMap)[pos.x][pos.y][pos.z]) - continue; // TODO needs to skip this check if we have artifacts-aura-like spell cast + continue; // TODO needs to skip this check if we have view-air-like spell cast if (!obj->visitableAt(pos.x, pos.y)) continue; @@ -668,12 +671,6 @@ void CMapHandler::CMapWorldViewBlitter::drawTileOverlay(SDL_Surface * targetSurf } } -void CMapHandler::CMapWorldViewBlitter::drawNormalObject(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect) const -{ - Rect scaledSourceRect(sourceRect->x * info->scale, sourceRect->y * info->scale, tileSize, tileSize); - CMapBlitter::drawNormalObject(targetSurf, sourceSurf, &scaledSourceRect); -} - void CMapHandler::CMapWorldViewBlitter::drawHeroFlag(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, SDL_Rect * destRect, bool moving) const { if (moving) @@ -682,13 +679,13 @@ void CMapHandler::CMapWorldViewBlitter::drawHeroFlag(SDL_Surface * targetSurf, S CMapBlitter::drawHeroFlag(targetSurf, sourceSurf, sourceRect, destRect, false); } -void CMapHandler::CMapWorldViewBlitter::drawHero(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, bool moving) const +void CMapHandler::CMapWorldViewBlitter::drawObject(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, bool moving) const { if (moving) return; Rect scaledSourceRect(sourceRect->x * info->scale, sourceRect->y * info->scale, sourceRect->w, sourceRect->h); - CMapBlitter::drawHero(targetSurf, sourceSurf, &scaledSourceRect, false); + CMapBlitter::drawObject(targetSurf, sourceSurf, &scaledSourceRect, false); } void CMapHandler::CMapBlitter::drawTileTerrain(SDL_Surface * targetSurf, const TerrainTile & tinfo, const TerrainTile2 & tile) const @@ -788,21 +785,15 @@ void CMapHandler::CMapBlitter::drawFrame(SDL_Surface * targetSurf) const drawElement(EMapCacheType::FRAME, parent->ttiles[pos.x][pos.y][topTile.z].terbitmap, nullptr, targetSurf, &destRect); } -void CMapHandler::CMapBlitter::drawNormalObject(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect) const -{ - Rect destRect(realTileRect); - drawElement(EMapCacheType::OBJECTS, sourceSurf, sourceRect, targetSurf, &destRect, true); -} - void CMapHandler::CMapBlitter::drawHeroFlag(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, SDL_Rect * destRect, bool moving) const { drawElement(EMapCacheType::HERO_FLAGS, sourceSurf, sourceRect, targetSurf, destRect, false); } -void CMapHandler::CMapBlitter::drawHero(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, bool moving) const +void CMapHandler::CMapBlitter::drawObject(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, bool moving) const { Rect dstRect(realTileRect); - drawElement(EMapCacheType::HEROES, sourceSurf, sourceRect, targetSurf, &dstRect, true); + drawElement(EMapCacheType::OBJECTS, sourceSurf, sourceRect, targetSurf, &dstRect, true); } void CMapHandler::CMapBlitter::drawObjects(SDL_Surface * targetSurf, const TerrainTile2 & tile) const @@ -810,7 +801,20 @@ void CMapHandler::CMapBlitter::drawObjects(SDL_Surface * targetSurf, const Terra auto & objects = tile.objects; for(auto & object : objects) { - const CGObjectInstance * obj = object.first; + if (object.fadeAnimKey >= 0) + { + auto fadeIter = parent->fadeAnims.find(object.fadeAnimKey); + if (fadeIter != parent->fadeAnims.end()) + { + // this object is currently fading, so skip normal drawing + Rect r2(realTileRect); + CFadeAnimation * fade = (*fadeIter).second.second; + fade->draw(targetSurf, nullptr, &r2); + continue; + } + } + + const CGObjectInstance * obj = object.obj; if (!graphics->getDef(obj)) processDef(obj->appearance); if (!graphics->getDef(obj) && !obj->appearance.animationFile.empty()) @@ -818,122 +822,28 @@ void CMapHandler::CMapBlitter::drawObjects(SDL_Surface * targetSurf, const Terra if (!canDrawObject(obj)) continue; - - PlayerColor color = obj->tempOwner; - - SDL_Rect pp = object.second; - pp.h = tileSize; - pp.w = tileSize; - - const CGHeroInstance * hero = (obj->ID != Obj::HERO - ? nullptr - : static_cast(obj)); - - //print hero / boat and flag - if((hero && hero->moveDir && hero->type) || (obj->ID == Obj::BOAT)) //it's hero or boat - { - const int IMGVAL = 8; //frames per group of movement animation - ui8 dir; - std::vector * iv = nullptr; - std::vector Graphics::*flg = nullptr; - SDL_Surface * tb = nullptr; //surface to blitted - - if(hero) //hero + + auto objData = findObjectBitmap(obj, info->anim); + if (objData.objBitmap) + { + Rect srcRect(object.rect.x, object.rect.y, tileSize, tileSize); + + drawObject(targetSurf, objData.objBitmap, &srcRect, objData.isMoving); + if (objData.flagBitmap) { - if(hero->tempOwner >= PlayerColor::PLAYER_LIMIT) //Neutral hero? + if (objData.isMoving) { - logGlobal->errorStream() << "A neutral hero (" << hero->name << ") at " << hero->pos << ". Should not happen!"; - continue; + srcRect.y += FRAMES_PER_MOVE_ANIM_GROUP * 2 - tileSize; + Rect dstRect(realPos.x, realPos.y - tileSize / 2, tileSize, tileSize); + drawHeroFlag(targetSurf, objData.flagBitmap, &srcRect, &dstRect, true); } - - dir = hero->moveDir; - - //pick graphics of hero (or boat if hero is sailing) - if (hero->boat) - iv = &graphics->boatAnims[hero->boat->subID]->ourImages; - else - iv = &graphics->heroAnims[hero->appearance.animationFile]->ourImages; - - //pick appropriate flag set - if(hero->boat) - { - switch (hero->boat->subID) - { - case 0: flg = &Graphics::flags1; break; - case 1: flg = &Graphics::flags2; break; - case 2: flg = &Graphics::flags3; break; - default: logGlobal->errorStream() << "Not supported boat subtype: " << hero->boat->subID; - } - } - else - { - flg = &Graphics::flags4; - } - } - else //boat - { - const CGBoat *boat = static_cast(obj); - dir = boat->direction; - iv = &graphics->boatAnims[boat->subID]->ourImages; - } - - if(hero && !hero->isStanding) //hero is moving - { - size_t gg; - for(gg=0; ggsize(); ++gg) - { - if((*iv)[gg].groupNumber == getHeroFrameNum(dir, true)) - { - tb = (*iv)[gg+info->getHeroAnim()%IMGVAL].bitmap; - break; - } - } - drawHero(targetSurf, tb, &pp, true); - - pp.y += IMGVAL * 2 - tileSize; - Rect destRect(realPos.x, realPos.y - tileSize / 2, tileSize, tileSize); - drawHeroFlag(targetSurf, (graphics->*flg)[color.getNum()]->ourImages[gg + info->getHeroAnim() % IMGVAL + 35].bitmap, &pp, &destRect, true); - - } - else //hero / boat stands still - { - size_t gg; - for(gg=0; gg < iv->size(); ++gg) - { - if((*iv)[gg].groupNumber == getHeroFrameNum(dir, false)) - { - tb = (*iv)[gg].bitmap; - break; - } - } - drawHero(targetSurf, tb, &pp, false); - - //printing flag - if(flg - && obj->pos.x == pos.x - && obj->pos.y == pos.y) + else if (obj->pos.x == pos.x && obj->pos.y == pos.y) { Rect dstRect(realPos.x - 2 * tileSize, realPos.y - tileSize, 3 * tileSize, 2 * tileSize); - if (dstRect.x - info->drawBounds->x > -tileSize * 2) - { - auto surf = (graphics->*flg)[color.getNum()]->ourImages - [getHeroFrameNum(dir, false) * 8 + (info->getHeroAnim() / 4) % IMGVAL].bitmap; - drawHeroFlag(targetSurf, surf, nullptr, &dstRect, false); - } + drawHeroFlag(targetSurf, objData.flagBitmap, nullptr, &dstRect, false); } } } - else //blit normal object - { - const std::vector &ourImages = graphics->getDef(obj)->ourImages; - SDL_Surface *bitmap = ourImages[(info->anim + getPhaseShift(obj)) % ourImages.size()].bitmap; - - //setting appropriate flag color - if(color < PlayerColor::PLAYER_LIMIT || color==PlayerColor::NEUTRAL) - CSDL_Ext::setPlayerColor(bitmap, color); - - drawNormalObject(targetSurf, bitmap, &pp); - } } } @@ -1086,6 +996,109 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn SDL_SetClipRect(targetSurf, &prevClip); } +CMapHandler::AnimBitmapHolder CMapHandler::CMapBlitter::findHeroBitmap(const CGHeroInstance * hero, int anim) const +{ + if(hero && hero->moveDir && hero->type) //it's hero or boat + { + if(hero->tempOwner >= PlayerColor::PLAYER_LIMIT) //Neutral hero? + { + logGlobal->errorStream() << "A neutral hero (" << hero->name << ") at " << hero->pos << ". Should not happen!"; + return CMapHandler::AnimBitmapHolder(); + } + + //pick graphics of hero (or boat if hero is sailing) + CDefEssential * def = nullptr; + if (hero->boat) + def = graphics->boatAnims[hero->boat->subID]; + else + def = graphics->heroAnims[hero->appearance.animationFile]; + + bool moving = !hero->isStanding; + int framesOffset = moving ? anim % FRAMES_PER_MOVE_ANIM_GROUP : 0; + int index = findAnimIndexByGroup(def, getHeroFrameNum(hero->moveDir, moving)); + if (index >= 0) + { + auto heroBitmap = def->ourImages[index + framesOffset].bitmap; + auto flagBitmap = findFlagBitmap(hero, anim, &hero->tempOwner, index + 35); + return CMapHandler::AnimBitmapHolder(heroBitmap, flagBitmap, moving); + } + } + return CMapHandler::AnimBitmapHolder(); +} + +CMapHandler::AnimBitmapHolder CMapHandler::CMapBlitter::findBoatBitmap(const CGBoat * boat, int anim) const +{ + auto def = graphics->boatAnims[boat->subID]; + int index = findAnimIndexByGroup(def, getHeroFrameNum(boat->direction, false)); + if (index < 0) + return CMapHandler::AnimBitmapHolder(); + return CMapHandler::AnimBitmapHolder(def->ourImages[index].bitmap); +} + +SDL_Surface * CMapHandler::CMapBlitter::findFlagBitmap(const CGHeroInstance * hero, int anim, const PlayerColor * color, int indexOffset) const +{ + if (!hero) + return nullptr; + + if (hero->boat) + return findBoatFlagBitmap(hero->boat, anim, color, indexOffset, hero->moveDir); + return findHeroFlagBitmap(hero, anim, color, indexOffset); +} + +SDL_Surface * CMapHandler::CMapBlitter::findHeroFlagBitmap(const CGHeroInstance * hero, int anim, const PlayerColor * color, int indexOffset) const +{ + return findFlagBitmapInternal(graphics->flags4[color->getNum()], anim, indexOffset, hero->moveDir, !hero->isStanding); +} + +SDL_Surface * CMapHandler::CMapBlitter::findBoatFlagBitmap(const CGBoat * boat, int anim, const PlayerColor * color, int indexOffset, ui8 dir) const +{ + std::vector Graphics::*flg = nullptr; + switch (boat->subID) + { + case 0: flg = &Graphics::flags1; break; + case 1: flg = &Graphics::flags2; break; + case 2: flg = &Graphics::flags3; break; + default: logGlobal->errorStream() << "Not supported boat subtype: " << boat->subID; return nullptr; + } + return findFlagBitmapInternal((graphics->*flg)[color->getNum()], anim, indexOffset, dir, false); +} + +SDL_Surface * CMapHandler::CMapBlitter::findFlagBitmapInternal(const CDefEssential * def, int anim, int indexOffset, ui8 dir, bool moving) const +{ + if (moving) + return def->ourImages[indexOffset + anim % FRAMES_PER_MOVE_ANIM_GROUP].bitmap; + return def->ourImages[getHeroFrameNum(dir, false) * FRAMES_PER_MOVE_ANIM_GROUP + (anim / 4) % FRAMES_PER_MOVE_ANIM_GROUP].bitmap; +} + +int CMapHandler::CMapBlitter::findAnimIndexByGroup(const CDefEssential * def, int groupNum) const +{ + auto iter = std::find_if(def->ourImages.begin(), def->ourImages.end(), [&](const Cimage &img){ return img.groupNumber == groupNum; }); + if (iter == def->ourImages.end()) + return -1; + return static_cast(iter - def->ourImages.begin()); +} + +CMapHandler::AnimBitmapHolder CMapHandler::CMapBlitter::findObjectBitmap(const CGObjectInstance * obj, int anim) const +{ + if (!obj) + return CMapHandler::AnimBitmapHolder(); + if (obj->ID == Obj::HERO) + return findHeroBitmap(static_cast(obj), anim); + if (obj->ID == Obj::BOAT) + return findBoatBitmap(static_cast(obj), anim); + + // normal object + + const std::vector &ourImages = graphics->getDef(obj)->ourImages; + SDL_Surface *bitmap = ourImages[(anim + getPhaseShift(obj)) % ourImages.size()].bitmap; + + //setting appropriate flag color + const PlayerColor &color = obj->tempOwner; + if(color < PlayerColor::PLAYER_LIMIT || color==PlayerColor::NEUTRAL) + CSDL_Ext::setPlayerColor(bitmap, color); + return CMapHandler::AnimBitmapHolder(bitmap); +} + ui8 CMapHandler::CMapBlitter::getPhaseShift(const CGObjectInstance *object) const { auto i = parent->animationPhase.find(object); @@ -1145,12 +1158,79 @@ std::pair CMapHandler::CMapBlitter::getVisBitmap() const } } -bool CMapHandler::printObject(const CGObjectInstance *obj) +bool CMapHandler::updateObjectsFade() +{ + for (auto iter = fadeAnims.begin(); iter != fadeAnims.end(); ) + { + int3 pos = (*iter).second.first; + CFadeAnimation * anim = (*iter).second.second; + + anim->update(); + + if (anim->isFading()) + ++iter; + else // fade finished + { + auto &objs = ttiles[pos.x][pos.y][pos.z].objects; + for (auto objIter = objs.begin(); objIter != objs.end(); ++objIter) + { + if ((*objIter).fadeAnimKey == (*iter).first) + { + if (anim->fadingMode == CFadeAnimation::EMode::OUT) + objs.erase(objIter); // if this was fadeout, remove the object from the map + else + (*objIter).fadeAnimKey = -1; // for fadein, just remove its connection to the finished fade + break; + } + } + iter = fadeAnims.erase(iter); + logAnim->traceStream() << "Fade anim finished, remaining: " << fadeAnims.size(); + } + } + + return !fadeAnims.empty(); +} + +bool CMapHandler::startObjectFade(TerrainTileObject & obj, bool in, int3 pos) +{ + SDL_Surface * fadeBitmap; + + auto objData = normalBlitter->findObjectBitmap(obj.obj, 0); + if (objData.objBitmap) + { + if (objData.isMoving) // ignore fading of moving objects (for now?) + { + logAnim->debugStream() << "Ignoring fade of moving object"; + return false; + } + + fadeBitmap = CSDL_Ext::newSurface(32, 32); // TODO cache these bitmaps instead of creating new ones? + Rect objSrcRect(obj.rect.x, obj.rect.y, 32, 32); + CSDL_Ext::blit8bppAlphaTo24bpp(objData.objBitmap, &objSrcRect, fadeBitmap, nullptr); + if (objData.flagBitmap) + { + if (obj.obj->pos.x - 1 == pos.x && obj.obj->pos.y - 1 == pos.y) // -1 to draw flag in top-center instead of right-bottom; kind of a hack + { + Rect flagSrcRect(32, 0, 32, 32); + CSDL_Ext::blitSurface(objData.flagBitmap, &flagSrcRect, fadeBitmap, nullptr); + } + } + auto anim = new CFadeAnimation(); + anim->init(in ? CFadeAnimation::EMode::IN : CFadeAnimation::EMode::OUT, fadeBitmap, true); + fadeAnims[++fadeAnimCounter] = std::pair(pos, anim); + obj.fadeAnimKey = fadeAnimCounter; + return true; + } + + return false; +} + +bool CMapHandler::printObject(const CGObjectInstance *obj, bool fadein /* = false */) { if (!graphics->getDef(obj)) processDef(obj->appearance); - const SDL_Surface *bitmap = graphics->getDef(obj)->ourImages[0].bitmap; + SDL_Surface *bitmap = graphics->getDef(obj)->ourImages[0].bitmap; const int tilesW = bitmap->w/32; const int tilesH = bitmap->h/32; @@ -1163,11 +1243,18 @@ bool CMapHandler::printObject(const CGObjectInstance *obj) cr.h = 32; cr.x = fx*32; cr.y = fy*32; - std::pair toAdd = std::make_pair(obj, cr); + TerrainTileObject toAdd(obj, cr); + if((obj->pos.x + fx - tilesW+1)>=0 && (obj->pos.x + fx - tilesW+1)pos.y + fy - tilesH+1)>=0 && (obj->pos.y + fy - tilesH+1)pos.x + fx - tilesW+1][obj->pos.y + fy - tilesH+1][obj->pos.z]; - + int3 pos(obj->pos.x + fx - tilesW + 1, obj->pos.y + fy - tilesH + 1, obj->pos.z); + TerrainTile2 & curt = ttiles[pos.x][pos.y][pos.z]; + + if (fadein && ADVOPT.objectFading) + { + startObjectFade(toAdd, true, pos); + } + auto i = curt.objects.begin(); for(; i != curt.objects.end(); i++) { @@ -1188,8 +1275,9 @@ bool CMapHandler::printObject(const CGObjectInstance *obj) return true; } -bool CMapHandler::hideObject(const CGObjectInstance *obj) +bool CMapHandler::hideObject(const CGObjectInstance *obj, bool fadeout /* = false */) { + // do we actually need to search through the whole map for this? for (size_t i=0; iwidth; i++) { for (size_t j=0; jheight; j++) @@ -1198,9 +1286,15 @@ bool CMapHandler::hideObject(const CGObjectInstance *obj) { for(size_t x=0; x < ttiles[i][j][k].objects.size(); x++) { - if (ttiles[i][j][k].objects[x].first->id == obj->id) + if (ttiles[i][j][k].objects[x].obj->id == obj->id) { - ttiles[i][j][k].objects.erase(ttiles[i][j][k].objects.begin() + x); + if (fadeout && ADVOPT.objectFading) // object should be faded == erase is delayed until the end of fadeout + { + if (!startObjectFade(ttiles[i][j][k].objects[x], false, int3(i, j, k))) + ttiles[i][j][k].objects.erase(ttiles[i][j][k].objects.begin() + x); + } + else + ttiles[i][j][k].objects.erase(ttiles[i][j][k].objects.begin() + x); break; } } @@ -1209,9 +1303,9 @@ bool CMapHandler::hideObject(const CGObjectInstance *obj) } return true; } -bool CMapHandler::removeObject(CGObjectInstance *obj) +bool CMapHandler::removeObject(CGObjectInstance *obj, bool fadeout /* = false */) { - hideObject(obj); + hideObject(obj, fadeout); return true; } @@ -1286,6 +1380,11 @@ ui8 CMapHandler::getDir(const int3 &a, const int3 &b) return -2; //shouldn't happen } +bool CMapHandler::canStartHeroMovement() +{ + return fadeAnims.empty(); // don't allow movement during fade animation +} + void shiftColors(SDL_Surface *img, int from, int howMany) //shifts colors in palette { //works with at most 16 colors, if needed more -> increase values @@ -1331,6 +1430,8 @@ CMapHandler::~CMapHandler() { delete graphics->FoWfullHide; delete graphics->FoWpartialHide; +// if (fadingOffscreenBitmapSurface) +// delete fadingOffscreenBitmapSurface; delete normalBlitter; delete worldViewBlitter; @@ -1347,6 +1448,11 @@ CMapHandler::~CMapHandler() for(int j=0; j < elem.size(); ++j) SDL_FreeSurface(elem[j]); } + + for (auto & elem : fadeAnims) + { + delete elem.second.second; + } terrainGraphics.clear(); } @@ -1358,6 +1464,7 @@ CMapHandler::CMapHandler() normalBlitter = new CMapNormalBlitter(this); worldViewBlitter = new CMapWorldViewBlitter(this); puzzleViewBlitter = new CMapPuzzleViewBlitter(this); + fadeAnimCounter = 0; } void CMapHandler::getTerrainDescr( const int3 &pos, std::string & out, bool terName ) @@ -1367,9 +1474,9 @@ void CMapHandler::getTerrainDescr( const int3 &pos, std::string & out, bool terN const TerrainTile &t = map->getTile(pos); for(auto & elem : tt.objects) { - if(elem.first->ID == Obj::HOLE) //Hole + if(elem.obj->ID == Obj::HOLE) //Hole { - out = elem.first->getObjectName(); + out = elem.obj->getObjectName(); return; } } @@ -1480,3 +1587,14 @@ bool CMapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObj return false; } +TerrainTileObject::TerrainTileObject(const CGObjectInstance * obj_, SDL_Rect rect_) + : obj(obj_), + rect(rect_), + fadeAnimKey(-1) +{ +} + +TerrainTileObject::~TerrainTileObject() +{ +} + diff --git a/client/mapHandler.h b/client/mapHandler.h index 0dfb05ace..1c2962d80 100644 --- a/client/mapHandler.h +++ b/client/mapHandler.h @@ -17,14 +17,16 @@ class CGObjectInstance; class CGHeroInstance; +class CGBoat; class CMap; class CGDefInfo; -class CGObjectInstance; class CDefHandler; struct TerrainTile; struct SDL_Surface; struct SDL_Rect; class CDefEssential; +class CFadeAnimation; +class PlayerColor; enum class EWorldViewIcon { @@ -50,11 +52,34 @@ enum class EWorldViewIcon }; +enum class EMapObjectFadingType +{ + NONE, + IN, + OUT +}; + +enum class EMapAnimRedrawStatus +{ + OK, + REDRAW_REQUESTED // map blitter requests quick redraw due to current animation +}; + +struct TerrainTileObject +{ + const CGObjectInstance *obj; + SDL_Rect rect; + int fadeAnimKey; + + TerrainTileObject(const CGObjectInstance *obj_, SDL_Rect rect_); + ~TerrainTileObject(); +}; + struct TerrainTile2 { SDL_Surface * terbitmap; //bitmap of terrain - std::vector < std::pair > objects; //pointers to objects being on this tile with rects to be easier to blit this tile on screen + std::vector objects; //pointers to objects being on this tile with rects to be easier to blit this tile on screen TerrainTile2(); }; @@ -156,11 +181,26 @@ class CMapHandler SDL_Surface * cacheWorldViewEntry(EMapCacheType type, intptr_t key, SDL_Surface * entry); intptr_t genKey(intptr_t realPtr, ui8 mod); }; + + /// helper struct to pass around resolved bitmaps of an object; surfaces can be nullptr if object doesn't have bitmap of that type + struct AnimBitmapHolder + { + SDL_Surface * objBitmap; // main object bitmap + SDL_Surface * flagBitmap; // flag bitmap for the object (probably only for heroes and boats with heroes) + bool isMoving; // indicates if the object is moving (again, heroes/boats only) + + AnimBitmapHolder(SDL_Surface * objBitmap_ = nullptr, SDL_Surface * flagBitmap_ = nullptr, bool moving = false) + : objBitmap(objBitmap_), + flagBitmap(flagBitmap_), + isMoving(moving) + {} + }; class CMapBlitter - { + { protected: + static constexpr int FRAMES_PER_MOVE_ANIM_GROUP = 8; CMapHandler * parent; // ptr to enclosing map handler; generally for legacy reasons, probably could/should be refactored out of here int tileSize; // size of a tile drawn on map [in pixels] int halfTileSizeCeil; // half of the tile size, rounded up @@ -187,10 +227,8 @@ class CMapHandler virtual void drawRoad(SDL_Surface * targetSurf, const TerrainTile & tinfo, const TerrainTile * tinfoUpper) const; /// draws all objects on current tile (higher-level logic, unlike other draw*** methods) virtual void drawObjects(SDL_Surface * targetSurf, const TerrainTile2 & tile) const; - /// current tile: draws non-hero object with given image/position - virtual void drawNormalObject(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect) const; + virtual void drawObject(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, bool moving) const; virtual void drawHeroFlag(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, SDL_Rect * destRect, bool moving) const; - virtual void drawHero(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, bool moving) const; // second drawing pass @@ -220,10 +258,22 @@ class CMapHandler virtual bool canDrawObject(const CGObjectInstance * obj) const; virtual bool canDrawCurrentTile() const; + + // internal helper methods to choose correct bitmap(s) for object; called internally by findObjectBitmap + AnimBitmapHolder findHeroBitmap(const CGHeroInstance * hero, int anim) const; + AnimBitmapHolder findBoatBitmap(const CGBoat * hero, int anim) const; + SDL_Surface * findFlagBitmap(const CGHeroInstance * obj, int anim, const PlayerColor * color, int indexOffset) const; + SDL_Surface * findHeroFlagBitmap(const CGHeroInstance * obj, int anim, const PlayerColor * color, int indexOffset) const; + SDL_Surface * findBoatFlagBitmap(const CGBoat * obj, int anim, const PlayerColor * color, int indexOffset, ui8 dir) const; + SDL_Surface * findFlagBitmapInternal(const CDefEssential * def, int anim, int indexOffset, ui8 dir, bool moving) const; + int findAnimIndexByGroup(const CDefEssential * def, int groupNum) const; + public: CMapBlitter(CMapHandler * p) : parent(p) {} virtual ~CMapBlitter(){} void blit(SDL_Surface * targetSurf, const MapDrawingInfo * info); + /// helper method that chooses correct bitmap(s) for given object + AnimBitmapHolder findObjectBitmap(const CGObjectInstance * obj, int anim) const; }; @@ -248,14 +298,13 @@ class CMapHandler SDL_Surface * targetSurf, SDL_Rect * destRect, bool alphaBlit = false, ui8 rotationInfo = 0u) const override; void drawTileOverlay(SDL_Surface * targetSurf, const TerrainTile2 & tile) const override; - void drawNormalObject(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect) const override; void drawHeroFlag(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, SDL_Rect * destRect, bool moving) const override; - void drawHero(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, bool moving) const; + void drawObject(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, bool moving) const override; void drawFrame(SDL_Surface * targetSurf) const override {} void init(const MapDrawingInfo * info) override; SDL_Rect clip(SDL_Surface * targetSurf) const override; - ui8 getHeroFrameNum(ui8 dir, bool isMoving) const override { return 0u; } +// ui8 getHeroFrameNum(ui8 dir, bool isMoving) const override { return 0u; } ui8 getPhaseShift(const CGObjectInstance *object) const override { return 0u; } void drawScaledRotatedElement(EMapCacheType type, SDL_Surface * baseSurf, SDL_Surface * targetSurf, ui8 rotation, @@ -283,8 +332,13 @@ class CMapHandler CMapBlitter * normalBlitter; CMapBlitter * worldViewBlitter; CMapBlitter * puzzleViewBlitter; + + std::map> fadeAnims; + int fadeAnimCounter; CMapBlitter * resolveBlitter(const MapDrawingInfo * info) const; + bool updateObjectsFade(); + bool startObjectFade(TerrainTileObject & obj, bool in, int3 pos); public: PseudoV< PseudoV< PseudoV > > ttiles; //informations about map tiles int3 sizes; //map size (x = width, y = height, z = number of levels) @@ -320,9 +374,9 @@ public: void getTerrainDescr(const int3 &pos, std::string & out, bool terName); //if tername == false => empty string when tile is clear CGObjectInstance * createObject(int id, int subid, int3 pos, int owner=254); //creates a new object with a certain id and subid - bool printObject(const CGObjectInstance * obj); //puts appropriate things to ttiles, so obj will be visible on map - bool hideObject(const CGObjectInstance * obj); //removes appropriate things from ttiles, so obj will be no longer visible on map (but still will exist) - bool removeObject(CGObjectInstance * obj); //removes object from each place in VCMI (I hope) + bool printObject(const CGObjectInstance * obj, bool fadein = false); //puts appropriate things to ttiles, so obj will be visible on map + bool hideObject(const CGObjectInstance * obj, bool fadeout = false); //removes appropriate things from ttiles, so obj will be no longer visible on map (but still will exist) + bool removeObject(CGObjectInstance * obj, bool fadeout = false); //removes object from each place in VCMI (I hope) void init(); void calculateBlockedPos(); void initObjectRects(); @@ -330,10 +384,12 @@ public: void roadsRiverTerrainInit(); void prepareFOWDefs(); - void drawTerrainRectNew(SDL_Surface * targetSurface, const MapDrawingInfo * info); + EMapAnimRedrawStatus drawTerrainRectNew(SDL_Surface * targetSurface, const MapDrawingInfo * info, bool redrawOnlyAnim = false); void updateWater(); void validateRectTerr(SDL_Rect * val, const SDL_Rect * ext); //terrainRect helper static ui8 getDir(const int3 & a, const int3 & b); //returns direction number in range 0 - 7 (0 is left top, clockwise) [direction: form a to b] + /// determines if the map is ready to handle new hero movement (not available during fading animations) + bool canStartHeroMovement(); void discardWorldViewCache(); diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 495f6f30a..2c45945f4 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -18,6 +18,7 @@ #include "../Graphics.h" #include "../mapHandler.h" +#include "../gui/CAnimation.h" #include "../gui/CCursorHandler.h" #include "../gui/CGuiHandler.h" #include "../gui/SDL_Extensions.h" @@ -61,7 +62,10 @@ CAdvMapInt *adventureInt; CTerrainRect::CTerrainRect() - : curHoveredTile(-1,-1,-1), currentPath(nullptr) + : fadeSurface(nullptr), + fadeAnim(new CFadeAnimation()), + curHoveredTile(-1,-1,-1), + currentPath(nullptr) { tilesw=(ADVOPT.advmapW+31)/32; tilesh=(ADVOPT.advmapH+31)/32; @@ -73,6 +77,13 @@ CTerrainRect::CTerrainRect() addUsedEvents(LCLICK | RCLICK | HOVER | MOVE); } +CTerrainRect::~CTerrainRect() +{ + if (fadeSurface) + SDL_FreeSurface(fadeSurface); + delete fadeAnim; +} + void CTerrainRect::deactivate() { CIntObject::deactivate(); @@ -272,8 +283,14 @@ void CTerrainRect::show(SDL_Surface * to) info.heroAnim = adventureInt->heroAnim; if (ADVOPT.smoothMove) info.movement = int3(moveX, moveY, 0); - - CGI->mh->drawTerrainRectNew(to, &info); + + lastRedrawStatus = CGI->mh->drawTerrainRectNew(to, &info); + if (fadeAnim->isFading()) + { + Rect r(pos); + fadeAnim->update(); + fadeAnim->draw(to, nullptr, &r); + } if (currentPath/* && adventureInt->position.z==currentPath->startPos().z*/) //drawing path { @@ -298,6 +315,14 @@ void CTerrainRect::showAll(SDL_Surface * to) } } +void CTerrainRect::showAnim(SDL_Surface * to) +{ + if (fadeAnim->isFading()) + show(to); + else if (lastRedrawStatus == EMapAnimRedrawStatus::REDRAW_REQUESTED) + show(to); // currently the same; maybe we should pass some flag to map handler so it redraws ONLY tiles that need redraw instead of full +} + int3 CTerrainRect::whichTileIsIt(const int & x, const int & y) { int3 ret; @@ -326,6 +351,24 @@ int3 CTerrainRect::tileCountOnScreen() } } +void CTerrainRect::fadeFromCurrentView() +{ + if (!ADVOPT.screenFading) + return; + if (adventureInt->mode == EAdvMapMode::WORLD_VIEW) + return; + + if (!fadeSurface) + fadeSurface = CSDL_Ext::newSurface(pos.w, pos.h); + SDL_BlitSurface(screen, &pos, fadeSurface, nullptr); + fadeAnim->init(CFadeAnimation::EMode::OUT, fadeSurface); +} + +bool CTerrainRect::needsAnimUpdate() +{ + return fadeAnim->isFading() || lastRedrawStatus == EMapAnimRedrawStatus::REDRAW_REQUESTED; +} + void CResDataBar::clickRight(tribool down, bool previousState) { } @@ -647,7 +690,7 @@ void CAdvMapInt::fsleepWake() void CAdvMapInt::fmoveHero() { const CGHeroInstance *h = curHero(); - if (!h || !terrain.currentPath) + if (!h || !terrain.currentPath || !CGI->mh->canStartHeroMovement()) return; LOCPLINT->moveHero(h, *terrain.currentPath); @@ -917,6 +960,13 @@ void CAdvMapInt::show(SDL_Surface * to) updateScreen=false; LOCPLINT->cingconsole->showAll(to); } + else if (terrain.needsAnimUpdate()) + { + terrain.showAnim(to); + for(int i=0;i<4;i++) + blitAt(gems[i]->ourImages[LOCPLINT->playerID.getNum()].bitmap,ADVOPT.gemX[i],ADVOPT.gemY[i],to); + } + infoBar.show(to); statusbar.showAll(to); } @@ -928,9 +978,14 @@ void CAdvMapInt::selectionChanged() select(to); } -void CAdvMapInt::centerOn(int3 on) +void CAdvMapInt::centerOn(int3 on, bool fade /* = false */) { bool switchedLevels = on.z != position.z; + + if (fade) + { + terrain.fadeFromCurrentView(); + } switch (mode) { @@ -962,9 +1017,9 @@ void CAdvMapInt::centerOn(int3 on) terrain.redraw(); } -void CAdvMapInt::centerOn(const CGObjectInstance *obj) +void CAdvMapInt::centerOn(const CGObjectInstance *obj, bool fade /* = false */) { - centerOn(obj->getSightCenter()); + centerOn(obj->getSightCenter(), fade); } void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key) @@ -1111,6 +1166,9 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key) #endif // VCMI_SDL1 if(k < 0 || k > 8) return; + + if (!CGI->mh->canStartHeroMovement()) + return; int3 dir = directions[k]; @@ -1381,7 +1439,8 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos) { if (terrain.currentPath && terrain.currentPath->endPos() == mapPos)//we'll be moving { - LOCPLINT->moveHero(currentHero,*terrain.currentPath); + if (CGI->mh->canStartHeroMovement()) + LOCPLINT->moveHero(currentHero,*terrain.currentPath); return; } else/* if(mp.z == currentHero->pos.z)*/ //remove old path and find a new one if we clicked on the map level on which hero is present diff --git a/client/windows/CAdvmapInterface.h b/client/windows/CAdvmapInterface.h index 377f0f7d4..ff4c28fa5 100644 --- a/client/windows/CAdvmapInterface.h +++ b/client/windows/CAdvmapInterface.h @@ -15,6 +15,8 @@ class CGTownInstance; class CHeroWindow; class CSpell; class IShipyard; +enum class EMapAnimRedrawStatus; +class CFadeAnimation; /*****************************/ @@ -48,12 +50,16 @@ public: class CTerrainRect : public CIntObject { + SDL_Surface * fadeSurface; + EMapAnimRedrawStatus lastRedrawStatus; + CFadeAnimation * fadeAnim; public: int tilesw, tilesh; //width and height of terrain to blit in tiles int3 curHoveredTile; int moveX, moveY; //shift between actual position of screen and the one we wil blit; ranges from -31 to 31 (in pixels) CTerrainRect(); + virtual ~CTerrainRect(); CGPath * currentPath; void deactivate(); void clickLeft(tribool down, bool previousState); @@ -62,11 +68,15 @@ public: void mouseMoved (const SDL_MouseMotionEvent & sEvent); void show(SDL_Surface * to); void showAll(SDL_Surface * to); + void showAnim(SDL_Surface * to); void showPath(const SDL_Rect * extRect, SDL_Surface * to); int3 whichTileIsIt(const int & x, const int & y); //x,y are cursor position int3 whichTileIsIt(); //uses current cursor pos /// @returns number of visible tiles on screen respecting current map scaling int3 tileCountOnScreen(); + /// animates view by caching current surface and crossfading it with normal screen + void fadeFromCurrentView(); + bool needsAnimUpdate(); }; /// Resources bar which shows information about how many gold, crystals,... you have @@ -176,8 +186,8 @@ public: void select(const CArmedInstance *sel, bool centerView = true); void selectionChanged(); - void centerOn(int3 on); - void centerOn(const CGObjectInstance *obj); + void centerOn(int3 on, bool fade = false); + void centerOn(const CGObjectInstance *obj, bool fade = false); int3 verifyPos(int3 ver); void handleRightClick(std::string text, tribool down); void keyPressed(const SDL_KeyboardEvent & key); diff --git a/config/resolutions.json b/config/resolutions.json index 8511694b0..1917a62da 100644 --- a/config/resolutions.json +++ b/config/resolutions.json @@ -4,7 +4,7 @@ { "resolution": { "x": 800, "y": 600 }, "InGameConsole": { "maxInputPerLine": 60, "maxOutputPerLine": 39 }, - "AdvMap": { "x": 7, "y": 7, "width": 594, "height": 546, "smoothMove": 1, "puzzleSepia": 1 }, + "AdvMap": { "x": 7, "y": 7, "width": 594, "height": 546, "smoothMove": 1, "puzzleSepia": 1, "objectFading" : 1, "screenFading" : 1 }, "InfoBox": { "x": 605, "y": 389 }, "gem0": { "x": 6, "y": 508, "graphic": "agemLL.def" }, "gem1": { "x": 556, "y": 508, "graphic": "agemLR.def" }, diff --git a/lib/CConfigHandler.cpp b/lib/CConfigHandler.cpp index 937732f57..b2f4f0194 100644 --- a/lib/CConfigHandler.cpp +++ b/lib/CConfigHandler.cpp @@ -228,6 +228,8 @@ void config::CConfigHandler::init() current->ac.advmapH = g["AdvMap"]["height"].Float(); current->ac.smoothMove = g["AdvMap"]["smoothMove"].Float(); current->ac.puzzleSepia = g["AdvMap"]["puzzleSepia"].Float(); + current->ac.screenFading = g["AdvMap"]["screenFading"].isNull() ? true : g["AdvMap"]["screenFading"].Float(); // enabled by default + current->ac.objectFading = g["AdvMap"]["objectFading"].isNull() ? true : g["AdvMap"]["objectFading"].Float(); current->ac.infoboxX = g["InfoBox"]["x"].Float(); current->ac.infoboxY = g["InfoBox"]["y"].Float(); diff --git a/lib/CConfigHandler.h b/lib/CConfigHandler.h index e1e75126d..1e85c44e0 100644 --- a/lib/CConfigHandler.h +++ b/lib/CConfigHandler.h @@ -133,6 +133,8 @@ namespace config int advmapX, advmapY, advmapW, advmapH; bool smoothMove; bool puzzleSepia; + bool screenFading; + bool objectFading; //general properties std::string mainGraphic; std::string worldViewGraphic;