diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index b879dddf6..2c0f7859d 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -588,6 +588,11 @@ bool MusicEntry::play() float timeToStart = owner->trackPositions[currentName]; startPosition = std::round(timeToStart * 1000); + // erase stored position: + // if music track will be interrupted again - new position will be written in stop() method + // if music track is not interrupted and will finish by timeout/end of file - it will restart from begginning as it should + owner->trackPositions.erase( owner->trackPositions.find(currentName) ); + if (Mix_FadeInMusicPos(music, 1, 1000, timeToStart) == -1) { logGlobal->error("Unable to play music (%s)", Mix_GetError()); diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index d7a37d8dd..7dea9ca28 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -218,27 +218,6 @@ void CPlayerInterface::yourTurn() acceptTurn(); } -STRONG_INLINE void subRect(const int & x, const int & y, const int & z, const SDL_Rect & r, const ObjectInstanceID & hid) -{ - TerrainTile2 & hlp = CGI->mh->ttiles[z][x][y]; - for (auto & elem : hlp.objects) - if (elem.obj && elem.obj->id == hid) - { - elem.rect = r; - return; - } -} - -STRONG_INLINE void delObjRect(const int & x, const int & y, const int & z, const ObjectInstanceID & hid) -{ - TerrainTile2 & hlp = CGI->mh->ttiles[z][x][y]; - for (int h=0; hid == hid) - { - hlp.objects.erase(hlp.objects.begin()+h); - return; - } -} void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose) { EVENT_HANDLER_CALLED_BY_CLIENT; @@ -1744,432 +1723,144 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns { auto subArr = (CGI->mh->ttiles)[hp.z]; - if (details.end.x+1 == details.start.x && details.end.y+1 == details.start.y) //tl + ho->isStanding = false; + + int heroWidth = ho->appearance->getWidth(); + int heroHeight = ho->appearance->getHeight(); + + int tileMinX = std::min(details.start.x, details.end.x) - heroWidth; + int tileMaxX = std::max(details.start.x, details.end.x); + int tileMinY = std::min(details.start.y, details.end.y) - heroHeight; + int tileMaxY = std::max(details.start.y, details.end.y); + + // determine tiles on which hero will be visible during movement and add hero as visible object on these tiles where necessary + for ( int tileX = tileMinX; tileX <= tileMaxX; ++tileX) { - //ho->moveDir = 1; - ho->isStanding = false; - subArr[hp.x-3][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, -31))); - subArr[hp.x-2][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 1, -31))); - subArr[hp.x-1][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 33, -31))); - subArr[hp.x][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 65, -31))); + for ( int tileY = tileMinY; tileY <= tileMaxY; ++tileY) + { + bool heroVisibleHere = false; + auto & tile = subArr[tileX][tileY]; - subArr[hp.x-3][hp.y-1].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); + for ( auto const & obj : tile.objects) + { + if (obj.obj == ho) + { + heroVisibleHere = true; + break; + } + } - subArr[hp.x-3][hp.y].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); - - std::stable_sort(subArr[hp.x-3][hp.y-2].objects.begin(), subArr[hp.x-3][hp.y-2].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x-2][hp.y-2].objects.begin(), subArr[hp.x-2][hp.y-2].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x-1][hp.y-2].objects.begin(), subArr[hp.x-1][hp.y-2].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x][hp.y-2].objects.begin(), subArr[hp.x][hp.y-2].objects.end(), objectBlitOrderSorter); - - std::stable_sort(subArr[hp.x-3][hp.y-1].objects.begin(), subArr[hp.x-3][hp.y-1].objects.end(), objectBlitOrderSorter); - - std::stable_sort(subArr[hp.x-3][hp.y].objects.begin(), subArr[hp.x-3][hp.y].objects.end(), objectBlitOrderSorter); - } - else if (details.end.x == details.start.x && details.end.y+1 == details.start.y) //t - { - //ho->moveDir = 2; - ho->isStanding = false; - subArr[hp.x-2][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 0, -31))); - subArr[hp.x-1][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 32, -31))); - subArr[hp.x][hp.y-2].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); - subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 64, 1), ho->id); - - subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 0, 33), ho->id); - subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 32, 33), ho->id); - subRect(hp.x, hp.y, hp.z, genRect(32, 32, 64, 33), ho->id); - - std::stable_sort(subArr[hp.x-2][hp.y-2].objects.begin(), subArr[hp.x-2][hp.y-2].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x-1][hp.y-2].objects.begin(), subArr[hp.x-1][hp.y-2].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x][hp.y-2].objects.begin(), subArr[hp.x][hp.y-2].objects.end(), objectBlitOrderSorter); - } - else if (details.end.x-1 == details.start.x && details.end.y+1 == details.start.y) //tr - { - //ho->moveDir = 3; - ho->isStanding = false; - subArr[hp.x-2][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -1, -31))); - subArr[hp.x-1][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 31, -31))); - subArr[hp.x][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 63, -31))); - subArr[hp.x+1][hp.y-2].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); - subArr[hp.x+1][hp.y-1].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); - subArr[hp.x+1][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 33))); - - std::stable_sort(subArr[hp.x-2][hp.y-2].objects.begin(), subArr[hp.x-2][hp.y-2].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x-1][hp.y-2].objects.begin(), subArr[hp.x-1][hp.y-2].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x][hp.y-2].objects.begin(), subArr[hp.x][hp.y-2].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x+1][hp.y-2].objects.begin(), subArr[hp.x+1][hp.y-2].objects.end(), objectBlitOrderSorter); - - std::stable_sort(subArr[hp.x+1][hp.y-1].objects.begin(), subArr[hp.x+1][hp.y-1].objects.end(), objectBlitOrderSorter); - - std::stable_sort(subArr[hp.x+1][hp.y].objects.begin(), subArr[hp.x+1][hp.y].objects.end(), objectBlitOrderSorter); - } - else if (details.end.x-1 == details.start.x && details.end.y == details.start.y) //r - { - //ho->moveDir = 4; - ho->isStanding = false; - 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); - subArr[hp.x+1][hp.y-1].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); - subArr[hp.x+1][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 32))); - - std::stable_sort(subArr[hp.x+1][hp.y-1].objects.begin(), subArr[hp.x+1][hp.y-1].objects.end(), objectBlitOrderSorter); - - std::stable_sort(subArr[hp.x+1][hp.y].objects.begin(), subArr[hp.x+1][hp.y].objects.end(), objectBlitOrderSorter); - } - else if (details.end.x-1 == details.start.x && details.end.y-1 == details.start.y) //br - { - //ho->moveDir = 5; - ho->isStanding = false; - 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); - subArr[hp.x+1][hp.y-1].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); - subArr[hp.x+1][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 31))); - - subArr[hp.x-2][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -1, 63))); - subArr[hp.x-1][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 31, 63))); - subArr[hp.x][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 63, 63))); - subArr[hp.x+1][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 63))); - - std::stable_sort(subArr[hp.x+1][hp.y-1].objects.begin(), subArr[hp.x+1][hp.y-1].objects.end(), objectBlitOrderSorter); - - std::stable_sort(subArr[hp.x+1][hp.y].objects.begin(), subArr[hp.x+1][hp.y].objects.end(), objectBlitOrderSorter); - - std::stable_sort(subArr[hp.x-2][hp.y+1].objects.begin(), subArr[hp.x-2][hp.y+1].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x-1][hp.y+1].objects.begin(), subArr[hp.x-1][hp.y+1].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x][hp.y+1].objects.begin(), subArr[hp.x][hp.y+1].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x+1][hp.y+1].objects.begin(), subArr[hp.x+1][hp.y+1].objects.end(), objectBlitOrderSorter); - } - else if (details.end.x == details.start.x && details.end.y-1 == details.start.y) //b - { - //ho->moveDir = 6; - ho->isStanding = false; - 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); - subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 64, -1), ho->id); - - subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 0, 31), ho->id); - 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); - - subArr[hp.x-2][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 0, 63))); - subArr[hp.x-1][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 32, 63))); - subArr[hp.x][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 64, 63))); - - std::stable_sort(subArr[hp.x-2][hp.y+1].objects.begin(), subArr[hp.x-2][hp.y+1].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x-1][hp.y+1].objects.begin(), subArr[hp.x-1][hp.y+1].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x][hp.y+1].objects.begin(), subArr[hp.x][hp.y+1].objects.end(), objectBlitOrderSorter); - } - else if (details.end.x+1 == details.start.x && details.end.y-1 == details.start.y) //bl - { - //ho->moveDir = 7; - ho->isStanding = false; - subArr[hp.x-3][hp.y-1].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); - - subArr[hp.x-3][hp.y].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); - - subArr[hp.x-3][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 63))); - subArr[hp.x-2][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 1, 63))); - subArr[hp.x-1][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 33, 63))); - subArr[hp.x][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 65, 63))); - - std::stable_sort(subArr[hp.x-3][hp.y-1].objects.begin(), subArr[hp.x-3][hp.y-1].objects.end(), objectBlitOrderSorter); - - std::stable_sort(subArr[hp.x-3][hp.y].objects.begin(), subArr[hp.x-3][hp.y].objects.end(), objectBlitOrderSorter); - - std::stable_sort(subArr[hp.x-3][hp.y+1].objects.begin(), subArr[hp.x-3][hp.y+1].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x-2][hp.y+1].objects.begin(), subArr[hp.x-2][hp.y+1].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x-1][hp.y+1].objects.begin(), subArr[hp.x-1][hp.y+1].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[hp.x][hp.y+1].objects.begin(), subArr[hp.x][hp.y+1].objects.end(), objectBlitOrderSorter); - } - else if (details.end.x+1 == details.start.x && details.end.y == details.start.y) //l - { - //ho->moveDir = 8; - ho->isStanding = false; - subArr[hp.x-3][hp.y-1].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); - - subArr[hp.x-3][hp.y].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); - - std::stable_sort(subArr[hp.x-3][hp.y-1].objects.begin(), subArr[hp.x-3][hp.y-1].objects.end(), objectBlitOrderSorter); - - std::stable_sort(subArr[hp.x-3][hp.y].objects.begin(), subArr[hp.x-3][hp.y].objects.end(), objectBlitOrderSorter); + if ( !heroVisibleHere) + { + tile.objects.push_back(TerrainTileObject(ho, {0,0,32,32})); + std::stable_sort(tile.objects.begin(), tile.objects.end(), objectBlitOrderSorter); + } + } } } void CPlayerInterface::movementPxStep( const TryMoveHero &details, int i, const int3 &hp, const CGHeroInstance * ho ) { - if (details.end.x+1 == details.start.x && details.end.y+1 == details.start.y) //tl + auto subArr = (CGI->mh->ttiles)[hp.z]; + + int heroWidth = ho->appearance->getWidth(); + int heroHeight = ho->appearance->getHeight(); + + int tileMinX = std::min(details.start.x, details.end.x) - heroWidth; + int tileMaxX = std::max(details.start.x, details.end.x); + int tileMinY = std::min(details.start.y, details.end.y) - heroHeight; + int tileMaxY = std::max(details.start.y, details.end.y); + + std::shared_ptr animation = graphics->getAnimation(ho); + + assert(animation); + assert(animation->size(0) != 0); + auto image = animation->getImage(0,0); + + int heroImageOldX = details.start.x * 32; + int heroImageOldY = details.start.y * 32; + + int heroImageNewX = details.end.x * 32; + int heroImageNewY = details.end.y * 32; + + int heroImageCurrX = heroImageOldX + i*(heroImageNewX - heroImageOldX)/32; + int heroImageCurrY = heroImageOldY + i*(heroImageNewY - heroImageOldY)/32; + + // recompute which part of hero sprite will be visible on each tile at this point of movement animation + for ( int tileX = tileMinX; tileX <= tileMaxX; ++tileX) { - //setting advmap shift - adventureInt->terrain.moveX = i-32; - adventureInt->terrain.moveY = i-32; + for ( int tileY = tileMinY; tileY <= tileMaxY; ++tileY) + { + auto & tile = subArr[tileX][tileY]; + for ( auto & obj : tile.objects) + { + if (obj.obj == ho) + { + int tilePosX = tileX * 32; + int tilePosY = tileY * 32; - subRect(hp.x-3, hp.y-2, hp.z, genRect(32, 32, -31+i, -31+i), ho->id); - subRect(hp.x-2, hp.y-2, hp.z, genRect(32, 32, 1+i, -31+i), ho->id); - subRect(hp.x-1, hp.y-2, hp.z, genRect(32, 32, 33+i, -31+i), ho->id); - subRect(hp.x, hp.y-2, hp.z, genRect(32, 32, 65+i, -31+i), ho->id); - - subRect(hp.x-3, hp.y-1, hp.z, genRect(32, 32, -31+i, 1+i), ho->id); - subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1+i, 1+i), ho->id); - subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33+i, 1+i), ho->id); - subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65+i, 1+i), ho->id); - - subRect(hp.x-3, hp.y, hp.z, genRect(32, 32, -31+i, 33+i), ho->id); - subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1+i, 33+i), ho->id); - subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33+i, 33+i), ho->id); - subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65+i, 33+i), ho->id); + obj.rect.x = tilePosX - heroImageCurrX + image->width() - 32; + obj.rect.y = tilePosY - heroImageCurrY + image->height() - 32; + } + } + } } - else if (details.end.x == details.start.x && details.end.y+1 == details.start.y) //t - { - //setting advmap shift - adventureInt->terrain.moveY = i-32; - subRect(hp.x-2, hp.y-2, hp.z, genRect(32, 32, 0, -31+i), ho->id); - subRect(hp.x-1, hp.y-2, hp.z, genRect(32, 32, 32, -31+i), ho->id); - subRect(hp.x, hp.y-2, hp.z, genRect(32, 32, 64, -31+i), ho->id); - - subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 0, 1+i), ho->id); - subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 32, 1+i), ho->id); - subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 64, 1+i), ho->id); - - subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 0, 33+i), ho->id); - subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 32, 33+i), ho->id); - subRect(hp.x, hp.y, hp.z, genRect(32, 32, 64, 33+i), ho->id); - } - else if (details.end.x-1 == details.start.x && details.end.y+1 == details.start.y) //tr - { - //setting advmap shift - adventureInt->terrain.moveX = -i+32; - adventureInt->terrain.moveY = i-32; - - subRect(hp.x-2, hp.y-2, hp.z, genRect(32, 32, -1-i, -31+i), ho->id); - subRect(hp.x-1, hp.y-2, hp.z, genRect(32, 32, 31-i, -31+i), ho->id); - subRect(hp.x, hp.y-2, hp.z, genRect(32, 32, 63-i, -31+i), ho->id); - subRect(hp.x+1, hp.y-2, hp.z, genRect(32, 32, 95-i, -31+i), ho->id); - - subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1-i, 1+i), ho->id); - subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31-i, 1+i), ho->id); - subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63-i, 1+i), ho->id); - subRect(hp.x+1, hp.y-1, hp.z, genRect(32, 32, 95-i, 1+i), ho->id); - - subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1-i, 33+i), ho->id); - subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31-i, 33+i), ho->id); - subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63-i, 33+i), ho->id); - subRect(hp.x+1, hp.y, hp.z, genRect(32, 32, 95-i, 33+i), ho->id); - } - else if (details.end.x-1 == details.start.x && details.end.y == details.start.y) //r - { - //setting advmap shift - adventureInt->terrain.moveX = -i+32; - - subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1-i, 0), ho->id); - subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31-i, 0), ho->id); - subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63-i, 0), ho->id); - subRect(hp.x+1, hp.y-1, hp.z, genRect(32, 32, 95-i, 0), ho->id); - - subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1-i, 32), ho->id); - subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31-i, 32), ho->id); - subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63-i, 32), ho->id); - subRect(hp.x+1, hp.y, hp.z, genRect(32, 32, 95-i, 32), ho->id); - } - else if (details.end.x-1 == details.start.x && details.end.y-1 == details.start.y) //br - { - - //setting advmap shift - adventureInt->terrain.moveX = -i+32; - adventureInt->terrain.moveY = -i+32; - - subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1-i, -1-i), ho->id); - subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31-i, -1-i), ho->id); - subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63-i, -1-i), ho->id); - subRect(hp.x+1, hp.y-1, hp.z, genRect(32, 32, 95-i, -1-i), ho->id); - - subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1-i, 31-i), ho->id); - subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31-i, 31-i), ho->id); - subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63-i, 31-i), ho->id); - subRect(hp.x+1, hp.y, hp.z, genRect(32, 32, 95-i, 31-i), ho->id); - - subRect(hp.x-2, hp.y+1, hp.z, genRect(32, 32, -1-i, 63-i), ho->id); - subRect(hp.x-1, hp.y+1, hp.z, genRect(32, 32, 31-i, 63-i), ho->id); - subRect(hp.x, hp.y+1, hp.z, genRect(32, 32, 63-i, 63-i), ho->id); - subRect(hp.x+1, hp.y+1, hp.z, genRect(32, 32, 95-i, 63-i), ho->id); - } - else if (details.end.x == details.start.x && details.end.y-1 == details.start.y) //b - { - //setting advmap shift - adventureInt->terrain.moveY = -i+32; - - subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 0, -1-i), ho->id); - subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 32, -1-i), ho->id); - subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 64, -1-i), ho->id); - - subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 0, 31-i), ho->id); - subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 32, 31-i), ho->id); - subRect(hp.x, hp.y, hp.z, genRect(32, 32, 64, 31-i), ho->id); - - subRect(hp.x-2, hp.y+1, hp.z, genRect(32, 32, 0, 63-i), ho->id); - subRect(hp.x-1, hp.y+1, hp.z, genRect(32, 32, 32, 63-i), ho->id); - subRect(hp.x, hp.y+1, hp.z, genRect(32, 32, 64, 63-i), ho->id); - } - else if (details.end.x+1 == details.start.x && details.end.y-1 == details.start.y) //bl - { - //setting advmap shift - adventureInt->terrain.moveX = i-32; - adventureInt->terrain.moveY = -i+32; - - subRect(hp.x-3, hp.y-1, hp.z, genRect(32, 32, -31+i, -1-i), ho->id); - subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1+i, -1-i), ho->id); - subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33+i, -1-i), ho->id); - subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65+i, -1-i), ho->id); - - subRect(hp.x-3, hp.y, hp.z, genRect(32, 32, -31+i, 31-i), ho->id); - subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1+i, 31-i), ho->id); - subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33+i, 31-i), ho->id); - subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65+i, 31-i), ho->id); - - subRect(hp.x-3, hp.y+1, hp.z, genRect(32, 32, -31+i, 63-i), ho->id); - subRect(hp.x-2, hp.y+1, hp.z, genRect(32, 32, 1+i, 63-i), ho->id); - subRect(hp.x-1, hp.y+1, hp.z, genRect(32, 32, 33+i, 63-i), ho->id); - subRect(hp.x, hp.y+1, hp.z, genRect(32, 32, 65+i, 63-i), ho->id); - } - else if (details.end.x+1 == details.start.x && details.end.y == details.start.y) //l - { - //setting advmap shift - adventureInt->terrain.moveX = i-32; - - subRect(hp.x-3, hp.y-1, hp.z, genRect(32, 32, -31+i, 0), ho->id); - subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1+i, 0), ho->id); - subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33+i, 0), ho->id); - subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65+i, 0), ho->id); - - subRect(hp.x-3, hp.y, hp.z, genRect(32, 32, -31+i, 32), ho->id); - subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1+i, 32), ho->id); - subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33+i, 32), ho->id); - subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65+i, 32), ho->id); - } + adventureInt->terrain.moveX = (32 - i) * (heroImageNewX - heroImageOldX) / 32; + adventureInt->terrain.moveY = (32 - i) * (heroImageNewY - heroImageOldY) / 32; } void CPlayerInterface::finishMovement( const TryMoveHero &details, const int3 &hp, const CGHeroInstance * ho ) { - adventureInt->terrain.moveX = adventureInt->terrain.moveY = 0; + auto subArr = (CGI->mh->ttiles)[hp.z]; - if (details.end.x+1 == details.start.x && details.end.y+1 == details.start.y) //tl + int heroWidth = ho->appearance->getWidth(); + int heroHeight = ho->appearance->getHeight(); + + int tileMinX = std::min(details.start.x, details.end.x) - heroWidth; + int tileMaxX = std::max(details.start.x, details.end.x); + int tileMinY = std::min(details.start.y, details.end.y) - heroHeight; + int tileMaxY = std::max(details.start.y, details.end.y); + + // erase hero from all tiles on which he is currently visible + for ( int tileX = tileMinX; tileX <= tileMaxX; ++tileX) { - delObjRect(hp.x, hp.y-2, hp.z, ho->id); - delObjRect(hp.x, hp.y-1, hp.z, ho->id); - delObjRect(hp.x, hp.y, hp.z, ho->id); - delObjRect(hp.x-1, hp.y, hp.z, ho->id); - delObjRect(hp.x-2, hp.y, hp.z, ho->id); - delObjRect(hp.x-3, hp.y, hp.z, ho->id); - } - else if (details.end.x == details.start.x && details.end.y+1 == details.start.y) //t - { - delObjRect(hp.x, hp.y, hp.z, ho->id); - delObjRect(hp.x-1, hp.y, hp.z, ho->id); - delObjRect(hp.x-2, hp.y, hp.z, ho->id); - } - else if (details.end.x-1 == details.start.x && details.end.y+1 == details.start.y) //tr - { - delObjRect(hp.x-2, hp.y-2, hp.z, ho->id); - delObjRect(hp.x-2, hp.y-1, hp.z, ho->id); - delObjRect(hp.x+1, hp.y, hp.z, ho->id); - delObjRect(hp.x, hp.y, hp.z, ho->id); - delObjRect(hp.x-1, hp.y, hp.z, ho->id); - delObjRect(hp.x-2, hp.y, hp.z, ho->id); - } - else if (details.end.x-1 == details.start.x && details.end.y == details.start.y) //r - { - delObjRect(hp.x-2, hp.y-1, hp.z, ho->id); - delObjRect(hp.x-2, hp.y, hp.z, ho->id); - } - else if (details.end.x-1 == details.start.x && details.end.y-1 == details.start.y) //br - { - delObjRect(hp.x-2, hp.y+1, hp.z, ho->id); - delObjRect(hp.x-2, hp.y, hp.z, ho->id); - delObjRect(hp.x+1, hp.y-1, hp.z, ho->id); - delObjRect(hp.x, hp.y-1, hp.z, ho->id); - delObjRect(hp.x-1, hp.y-1, hp.z, ho->id); - delObjRect(hp.x-2, hp.y-1, hp.z, ho->id); - } - else if (details.end.x == details.start.x && details.end.y-1 == details.start.y) //b - { - delObjRect(hp.x, hp.y-1, hp.z, ho->id); - delObjRect(hp.x-1, hp.y-1, hp.z, ho->id); - delObjRect(hp.x-2, hp.y-1, hp.z, ho->id); - } - else if (details.end.x+1 == details.start.x && details.end.y-1 == details.start.y) //bl - { - delObjRect(hp.x, hp.y-1, hp.z, ho->id); - delObjRect(hp.x-1, hp.y-1, hp.z, ho->id); - delObjRect(hp.x-2, hp.y-1, hp.z, ho->id); - delObjRect(hp.x-3, hp.y-1, hp.z, ho->id); - delObjRect(hp.x, hp.y, hp.z, ho->id); - delObjRect(hp.x, hp.y+1, hp.z, ho->id); - } - else if (details.end.x+1 == details.start.x && details.end.y == details.start.y) //l - { - delObjRect(hp.x, hp.y-1, hp.z, ho->id); - delObjRect(hp.x, hp.y, hp.z, ho->id); + for ( int tileY = tileMinY; tileY <= tileMaxY; ++tileY) + { + auto & tile = subArr[tileX][tileY]; + for (size_t i = 0; i < tile.objects.size(); ++i) + { + if ( tile.objects[i].obj == ho) + { + tile.objects.erase(tile.objects.begin() + i); + break; + } + } + } } - //restoring good rects - subRect(details.end.x-2, details.end.y-1, details.end.z, genRect(32, 32, 0, 0), ho->id); - subRect(details.end.x-1, details.end.y-1, details.end.z, genRect(32, 32, 32, 0), ho->id); - subRect(details.end.x, details.end.y-1, details.end.z, genRect(32, 32, 64, 0), ho->id); + // re-add hero to all tiles on which he will still be visible after animation is over + for ( int tileX = details.end.x - heroWidth + 1; tileX <= details.end.x; ++tileX) + { + for ( int tileY = details.end.y - heroHeight + 1; tileY <= details.end.y; ++tileY) + { + auto & tile = subArr[tileX][tileY]; + tile.objects.push_back(TerrainTileObject(ho, {0,0,32,32})); + } + } - subRect(details.end.x-2, details.end.y, details.end.z, genRect(32, 32, 0, 32), ho->id); - subRect(details.end.x-1, details.end.y, details.end.z, genRect(32, 32, 32, 32), ho->id); - subRect(details.end.x, details.end.y, details.end.z, genRect(32, 32, 64, 32), ho->id); + // update object list on all tiles that were affected during previous operations + for ( int tileX = tileMinX; tileX <= tileMaxX; ++tileX) + { + for ( int tileY = tileMinY; tileY <= tileMaxY; ++tileY) + { + auto & tile = subArr[tileX][tileY]; + std::stable_sort(tile.objects.begin(), tile.objects.end(), objectBlitOrderSorter); + } + } - //restoring good order of objects - - boost::detail::multi_array::sub_array subArr = (CGI->mh->ttiles)[details.end.z]; - - std::stable_sort(subArr[details.end.x-2][details.end.y-1].objects.begin(), subArr[details.end.x-2][details.end.y-1].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[details.end.x-1][details.end.y-1].objects.begin(), subArr[details.end.x-1][details.end.y-1].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[details.end.x][details.end.y-1].objects.begin(), subArr[details.end.x][details.end.y-1].objects.end(), objectBlitOrderSorter); - - std::stable_sort(subArr[details.end.x-2][details.end.y].objects.begin(), subArr[details.end.x-2][details.end.y].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[details.end.x-1][details.end.y].objects.begin(), subArr[details.end.x-1][details.end.y].objects.end(), objectBlitOrderSorter); - std::stable_sort(subArr[details.end.x][details.end.y].objects.begin(), subArr[details.end.x][details.end.y].objects.end(), objectBlitOrderSorter); + //recompute hero sprite positioning using hero's final position + movementPxStep(details, 32, hp, ho); } void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult ) diff --git a/client/battle/CBattleAnimations.cpp b/client/battle/CBattleAnimations.cpp index b2516fa55..935e02a92 100644 --- a/client/battle/CBattleAnimations.cpp +++ b/client/battle/CBattleAnimations.cpp @@ -785,22 +785,25 @@ bool CShootingAnimation::init() if (projectileAngle > straightAngle) { //upper shot - spi.x = fromPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier; - spi.y = fromPos.y + 265 + shooterInfo->animation.upperRightMissleOffsetY; + spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier; + spi.y0 = fromPos.y + 265 + shooterInfo->animation.upperRightMissleOffsetY; } else if (projectileAngle < -straightAngle) { //lower shot - spi.x = fromPos.x + 222 + ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier; - spi.y = fromPos.y + 265 + shooterInfo->animation.lowerRightMissleOffsetY; + spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier; + spi.y0 = fromPos.y + 265 + shooterInfo->animation.lowerRightMissleOffsetY; } else { //straight shot - spi.x = fromPos.x + 222 + ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier; - spi.y = fromPos.y + 265 + shooterInfo->animation.rightMissleOffsetY; + spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier; + spi.y0 = fromPos.y + 265 + shooterInfo->animation.rightMissleOffsetY; } + spi.x = spi.x0; + spi.y = spi.y0; + destPos += Point(225, 225); // recalculate angle taking in account offsets @@ -811,7 +814,9 @@ bool CShootingAnimation::init() if (attackedStack) { double animSpeed = AnimationControls::getProjectileSpeed(); // flight speed of projectile - spi.lastStep = static_cast(sqrt(static_cast((destPos.x - spi.x) * (destPos.x - spi.x) + (destPos.y - spi.y) * (destPos.y - spi.y))) / animSpeed); + double distanceSquared = (destPos.x - spi.x) * (destPos.x - spi.x) + (destPos.y - spi.y) * (destPos.y - spi.y); + double distance = sqrt(distanceSquared); + spi.lastStep = std::round(distance / animSpeed); if(spi.lastStep == 0) spi.lastStep = 1; spi.dx = (destPos.x - spi.x) / spi.lastStep; @@ -837,30 +842,42 @@ bool CShootingAnimation::init() } double pi = boost::math::constants::pi(); - if (owner->idToProjectile.count(spi.creID) == 0) //in some cases (known one: hero grants shooter bonus to unit) the shooter stack's projectile may not be properly initialized + //in some cases (known one: hero grants shooter bonus to unit) the shooter stack's projectile may not be properly initialized + if (!owner->idToProjectile.count(spi.creID) && !owner->idToRay.count(spi.creID)) owner->initStackProjectile(shooter); - // only frames below maxFrame are usable: anything higher is either no present or we don't know when it should be used - size_t maxFrame = std::min(angles.size(), owner->idToProjectile.at(spi.creID)->size(0)); - - assert(maxFrame > 0); - - // values in angles array indicate position from which this frame was rendered, in degrees. - // find frame that has closest angle to one that we need for this shot - size_t bestID = 0; - double bestDiff = fabs( angles[0] / 180 * pi - projectileAngle ); - - for (size_t i=1; iidToProjectile.count(spi.creID)) { - double currentDiff = fabs( angles[i] / 180 * pi - projectileAngle ); - if (currentDiff < bestDiff) - { - bestID = i; - bestDiff = currentDiff; - } - } + // only frames below maxFrame are usable: anything higher is either no present or we don't know when it should be used + size_t maxFrame = std::min(angles.size(), owner->idToProjectile.at(spi.creID)->size(0)); - spi.frameNum = static_cast(bestID); + assert(maxFrame > 0); + + // values in angles array indicate position from which this frame was rendered, in degrees. + // find frame that has closest angle to one that we need for this shot + size_t bestID = 0; + double bestDiff = fabs( angles[0] / 180 * pi - projectileAngle ); + + for (size_t i=1; i(bestID); + } + else if (owner->idToRay.count(spi.creID)) + { + // no-op + } + else + { + logGlobal->error("Unable to find valid projectile for shooter %d", spi.creID); + } // Set projectile animation start delay which is specified in frames spi.animStartDelay = shooterInfo->animation.attackClimaxFrame; diff --git a/client/battle/CBattleAnimations.h b/client/battle/CBattleAnimations.h index c71d78195..fce98674d 100644 --- a/client/battle/CBattleAnimations.h +++ b/client/battle/CBattleAnimations.h @@ -189,6 +189,7 @@ public: /// Small struct which contains information about the position and the velocity of a projectile struct ProjectileInfo { + double x0, y0; //initial position on the screen double x, y; //position on the screen double dx, dy; //change in position in one step int step, lastStep; //to know when finish showing this projectile diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index b02d7153c..478bd4eba 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -1020,15 +1020,22 @@ void CBattleInterface::initStackProjectile(const CStack * stack) else creature = stack->getCreature(); - std::shared_ptr projectile = std::make_shared(creature->animation.projectileImageName); - projectile->preload(); + if (creature->animation.projectileRay.empty()) + { + std::shared_ptr projectile = std::make_shared(creature->animation.projectileImageName); + projectile->preload(); - if(projectile->size(1) != 0) - logAnim->error("Expected empty group 1 in stack projectile"); + if(projectile->size(1) != 0) + logAnim->error("Expected empty group 1 in stack projectile"); + else + projectile->createFlippedGroup(0, 1); + + idToProjectile[stack->getCreature()->idNumber] = projectile; + } else - projectile->createFlippedGroup(0, 1); - - idToProjectile[stack->getCreature()->idNumber] = projectile; + { + idToRay[stack->getCreature()->idNumber] = creature->animation.projectileRay; + } } void CBattleInterface::stackRemoved(uint32_t stackID) @@ -3206,23 +3213,63 @@ void CBattleInterface::showProjectiles(SDL_Surface *to) continue; // wait... } - size_t group = it->reverse ? 1 : 0; - auto image = idToProjectile[it->creID]->getImage(it->frameNum, group, true); - - if(image) + if (idToProjectile.count(it->creID)) { - SDL_Rect dst; - dst.h = image->height(); - dst.w = image->width(); - dst.x = static_cast(it->x - dst.w / 2); - dst.y = static_cast(it->y - dst.h / 2); + size_t group = it->reverse ? 1 : 0; + auto image = idToProjectile[it->creID]->getImage(it->frameNum, group, true); - image->draw(to, &dst, nullptr); + if(image) + { + SDL_Rect dst; + dst.h = image->height(); + dst.w = image->width(); + dst.x = static_cast(it->x - dst.w / 2); + dst.y = static_cast(it->y - dst.h / 2); + + image->draw(to, &dst, nullptr); + } + } + if (idToRay.count(it->creID)) + { + auto const & ray = idToRay[it->creID]; + + if (std::abs(it->dx) > std::abs(it->dy)) // draw in horizontal axis + { + int y1 = it->y0 - ray.size() / 2; + int y2 = it->y - ray.size() / 2; + + int x1 = it->x0; + int x2 = it->x; + + for (size_t i = 0; i < ray.size(); ++i) + { + SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1}; + SDL_Color endColor { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2}; + + CSDL_Ext::drawLine(to, x1, y1 + i, x2, y2 + i, beginColor, endColor); + } + } + else // draw in vertical axis + { + int x1 = it->x0 - ray.size() / 2; + int x2 = it->x - ray.size() / 2; + + int y1 = it->y0; + int y2 = it->y; + + for (size_t i = 0; i < ray.size(); ++i) + { + SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1}; + SDL_Color endColor { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2}; + + CSDL_Ext::drawLine(to, x1 + i, y1, x2 + i, y2, beginColor, endColor); + } + } } // Update projectile ++it->step; - if (it->step == it->lastStep) + if (it->step > it->lastStep) { toBeDeleted.insert(toBeDeleted.end(), it); } diff --git a/client/battle/CBattleInterface.h b/client/battle/CBattleInterface.h index 0fdd232fe..4a7e50d57 100644 --- a/client/battle/CBattleInterface.h +++ b/client/battle/CBattleInterface.h @@ -17,6 +17,7 @@ #include "CBattleAnimations.h" #include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation +#include "../../lib/CCreatureHandler.h" #include "../../lib/battle/CBattleInfoCallback.h" VCMI_LIB_NAMESPACE_BEGIN @@ -148,6 +149,7 @@ private: std::map> creAnims; //animations of creatures from fighting armies (order by BattleInfo's stacks' ID) std::map> idToProjectile; + std::map> idToRay; std::map> animationsCache; std::map> obstacleAnimations; diff --git a/client/gui/SDL_Extensions.cpp b/client/gui/SDL_Extensions.cpp index 410ddb4b8..6cb332865 100644 --- a/client/gui/SDL_Extensions.cpp +++ b/client/gui/SDL_Extensions.cpp @@ -361,6 +361,75 @@ void CSDL_Ext::update(SDL_Surface * what) if(0 !=SDL_UpdateTexture(screenTexture, nullptr, what->pixels, what->pitch)) logGlobal->error("%s SDL_UpdateTexture %s", __FUNCTION__, SDL_GetError()); } + +template +Int lerp(Int a, Int b, float f) +{ + return a + std::round((b - a) * f); +} + +static void drawLineX(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2) +{ + for(int x = x1; x <= x2; x++) + { + float f = float(x - x1) / float(x2 - x1); + int y = lerp(y1, y2, f); + + uint8_t r = lerp(color1.r, color2.r, f); + uint8_t g = lerp(color1.g, color2.g, f); + uint8_t b = lerp(color1.b, color2.b, f); + uint8_t a = lerp(color1.a, color2.a, f); + + Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y); + ColorPutter<4, 0>::PutColor(p, r,g,b,a); + } +} + +static void drawLineY(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2) +{ + for(int y = y1; y <= y2; y++) + { + float f = float(y - y1) / float(y2 - y1); + int x = lerp(x1, x2, f); + + uint8_t r = lerp(color1.r, color2.r, f); + uint8_t g = lerp(color1.g, color2.g, f); + uint8_t b = lerp(color1.b, color2.b, f); + uint8_t a = lerp(color1.a, color2.a, f); + + Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y); + ColorPutter<4, 0>::PutColor(p, r,g,b,a); + } +} + +void CSDL_Ext::drawLine(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2) +{ + int width = std::abs(x1-x2); + int height = std::abs(y1-y2); + + if ( width == 0 && height == 0) + { + Uint8 *p = CSDL_Ext::getPxPtr(sur, x1, y1); + ColorPutter<4, 0>::PutColorAlpha(p, color1); + return; + } + + if (width > height) + { + if ( x1 < x2) + drawLineX(sur, x1,y1,x2,y2, color1, color2); + else + drawLineX(sur, x2,y2,x1,y1, color2, color1); + } + else + { + if ( y1 < y2) + drawLineY(sur, x1,y1,x2,y2, color1, color2); + else + drawLineY(sur, x2,y2,x1,y1, color2, color1); + } +} + void CSDL_Ext::drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const int3 &color) { for(int i = 0; i < w; i++) diff --git a/client/gui/SDL_Extensions.h b/client/gui/SDL_Extensions.h index bc5401a37..abcd02909 100644 --- a/client/gui/SDL_Extensions.h +++ b/client/gui/SDL_Extensions.h @@ -236,6 +236,7 @@ namespace CSDL_Ext SDL_Color makeColor(ui8 r, ui8 g, ui8 b, ui8 a); void update(SDL_Surface * what = screen); //updates whole surface (default - main screen) + void drawLine(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2); void drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const int3 &color); void drawBorder(SDL_Surface * sur, const SDL_Rect &r, const int3 &color); void drawDashedBorder(SDL_Surface * sur, const Rect &r, const int3 &color); diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 730ce0397..d945388cf 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -107,6 +107,10 @@ RandomMapTab::RandomMapTab() groupCompOnlyPlayers->addCallback([&](int btnId) { mapGenOptions->setCompOnlyPlayerCount(btnId); + + // deactive some MaxPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I + deactivateButtonsFrom(groupMaxPlayers.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1); + deactivateButtonsFrom(groupCompOnlyTeams.get(), (btnId == 0 ? 1 : btnId)); validateCompOnlyPlayersCnt(btnId); updateMapInfoByHost(); diff --git a/config/creatures/dungeon.json b/config/creatures/dungeon.json index 6b486abb7..3c4f5a98f 100644 --- a/config/creatures/dungeon.json +++ b/config/creatures/dungeon.json @@ -135,7 +135,14 @@ "animation": "CBEHOL.DEF", "missile" : { - "projectile": "SMBALX.DEF" + "ray" : + [ + { "start" : [ 160, 160, 160, 255 ], "end" : [ 160, 160, 160, 64 ] }, + { "start" : [ 192, 192, 192, 255 ], "end" : [ 192, 192, 192, 128 ] }, + { "start" : [ 224, 224, 224, 255 ], "end" : [ 224, 224, 224, 255 ] }, + { "start" : [ 192, 192, 192, 255 ], "end" : [ 192, 192, 192, 128 ] }, + { "start" : [ 160, 160, 160, 255 ], "end" : [ 160, 160, 160, 64 ] } + ] } }, "sound" : @@ -158,7 +165,14 @@ "animation": "CEVEYE.DEF", "missile" : { - "projectile": "SMBALX.DEF" + "ray" : + [ + { "start" : [ 160, 160, 160, 255 ], "end" : [ 160, 160, 160, 64 ] }, + { "start" : [ 192, 192, 192, 255 ], "end" : [ 192, 192, 192, 128 ] }, + { "start" : [ 224, 224, 224, 255 ], "end" : [ 224, 224, 224, 255 ] }, + { "start" : [ 192, 192, 192, 255 ], "end" : [ 192, 192, 192, 128 ] }, + { "start" : [ 160, 160, 160, 255 ], "end" : [ 160, 160, 160, 64 ] } + ] } }, "sound" : diff --git a/config/creatures/tower.json b/config/creatures/tower.json index 9e94bfa7d..8557127a1 100644 --- a/config/creatures/tower.json +++ b/config/creatures/tower.json @@ -208,7 +208,15 @@ "animation": "CAMAGE.DEF", "missile" : { - "projectile": "PMAGEX.DEF" + "attackClimaxFrame" : 8, + "ray" : + [ + { "start" : [ 160, 192, 0, 255 ], "end" : [ 160, 192, 0, 64 ] }, + { "start" : [ 128, 224, 128, 255 ], "end" : [ 128, 224, 128, 128 ] }, + { "start" : [ 32, 176, 32, 255 ], "end" : [ 32, 176, 32, 255 ] }, + { "start" : [ 128, 224, 128, 255 ], "end" : [ 128, 224, 128, 128 ] }, + { "start" : [ 160, 192, 0, 255 ], "end" : [ 160, 192, 0, 64 ] } + ] } }, "sound" : diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 4ab7b7a4e..8d47f61d0 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -27,7 +27,7 @@ "base" : { "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], - "mask" : [ "VV", "AV"] + "mask" : [ "VVV", "VAV"] }, "sounds" : { "removal" : ["KILLFADE"] diff --git a/config/schemas/creature.json b/config/schemas/creature.json index b5040720d..a57766839 100644 --- a/config/schemas/creature.json +++ b/config/schemas/creature.json @@ -217,7 +217,7 @@ "missile": { "type":"object", "additionalProperties" : false, - "required" : [ "projectile", "frameAngles", "offset", "attackClimaxFrame" ], + "required" : [ "frameAngles", "offset", "attackClimaxFrame" ], "description": "Missile description for archers", "properties":{ "projectile": { @@ -225,6 +225,37 @@ "description": "Path to projectile animation", "format" : "defFile" }, + "ray": { + "type":"array", + "description": "Colors of ray projectile animation", + "minItems" : 1, + "items": { + "type":"object", + "required" : [ "start", "end" ], + "properties":{ + "start": { + "type":"array", + "minItems" : 4, + "maxItems" : 4, + "items": { + "minimum" : 0, + "maximum" : 255, + "type":"number" + } + }, + "end": { + "type":"array", + "minItems" : 4, + "maxItems" : 4, + "items": { + "minimum" : 0, + "maximum" : 255, + "type":"number" + } + } + } + } + }, "frameAngles": { "type":"array", "description": "Angles of missile images, should go from 90 to -90", diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index bf1f43bc7..8f1c08518 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -907,6 +907,23 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c creature->animation.projectileImageName = config["graphics"]["missile"]["projectile"].String(); + for(const JsonNode & value : config["graphics"]["missile"]["ray"].Vector()) + { + CCreature::CreatureAnimation::RayColor color; + + color.r1 = value["start"].Vector()[0].Integer(); + color.g1 = value["start"].Vector()[1].Integer(); + color.b1 = value["start"].Vector()[2].Integer(); + color.a1 = value["start"].Vector()[3].Integer(); + + color.r2 = value["end"].Vector()[0].Integer(); + color.g2 = value["end"].Vector()[1].Integer(); + color.b2 = value["end"].Vector()[2].Integer(); + color.a2 = value["end"].Vector()[3].Integer(); + + creature->animation.projectileRay.push_back(color); + } + creature->special = config["special"].Bool() || config["disabled"].Bool(); const JsonNode & sounds = config["sound"]; diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index edf730767..c621d617e 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -63,6 +63,16 @@ public: struct CreatureAnimation { + struct RayColor { + uint8_t r1, g1, b1, a1; + uint8_t r2, g2, b2, a2; + + template void serialize(Handler &h, const int version) + { + h & r1 & g1 & b1 & a1 & r2 & g2 & b2 & a2; + } + }; + double timeBetweenFidgets, idleAnimationTime, walkAnimationTime, attackAnimationTime, flightAnimationDistance; int upperRightMissleOffsetX, rightMissleOffsetX, lowerRightMissleOffsetX, @@ -72,6 +82,7 @@ public: int troopCountLocationOffset, attackClimaxFrame; std::string projectileImageName; + std::vector projectileRay; //bool projectileSpin; //if true, appropriate projectile is spinning during flight template void serialize(Handler &h, const int version) @@ -91,6 +102,7 @@ public: h & troopCountLocationOffset; h & attackClimaxFrame; h & projectileImageName; + h & projectileRay; } } animation; diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index 774f374d5..141e61782 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -70,7 +70,7 @@ void CMapGenOptions::setPlayerCount(si8 value) assert((value >= 1 && value <= PlayerColor::PLAYER_LIMIT_I) || value == RANDOM_SIZE); playerCount = value; - auto possibleCompPlayersCount = value; + auto possibleCompPlayersCount = PlayerColor::PLAYER_LIMIT_I - value; if (compOnlyPlayerCount > possibleCompPlayersCount) setCompOnlyPlayerCount(possibleCompPlayersCount); diff --git a/mapeditor/Animation.cpp b/mapeditor/Animation.cpp index 0fa711a8f..ec844d3d5 100644 --- a/mapeditor/Animation.cpp +++ b/mapeditor/Animation.cpp @@ -450,10 +450,10 @@ void ImageLoader::init(QPoint SpriteSize, QPoint Margins, QPoint FullSize) margins = Margins; fullSize = FullSize; - memset((void *)image->bits(), 0, fullSize.y() * fullSize.x()); + memset((void *)image->bits(), 0, fullSize.y() * image->bytesPerLine()); lineStart = image->bits(); - lineStart += margins.y() * fullSize.x() + margins.x(); + lineStart += margins.y() * image->bytesPerLine() + margins.x(); position = lineStart; } @@ -477,7 +477,7 @@ inline void ImageLoader::Load(size_t size, ui8 color) inline void ImageLoader::EndLine() { - lineStart += fullSize.x(); + lineStart += image->bytesPerLine(); position = lineStart; } diff --git a/mapeditor/BitmapHandler.cpp b/mapeditor/BitmapHandler.cpp index 59e679de6..d1b9dd5ce 100644 --- a/mapeditor/BitmapHandler.cpp +++ b/mapeditor/BitmapHandler.cpp @@ -63,7 +63,7 @@ namespace BitmapHandler { it = 0xC; //auto bitmap = QBitmap::fromData(qsize, pcx + it); - QImage image(pcx + it, width, height, QImage::Format_Indexed8); + QImage image(pcx + it, width, height, width, QImage::Format_Indexed8); //palette - last 256*3 bytes QVector colorTable; @@ -81,7 +81,7 @@ namespace BitmapHandler } else { - QImage image(pcx + it, width, height, QImage::Format_RGB32); + QImage image(pcx + it, width, height, width * 3, QImage::Format_RGB888); return image; } } @@ -117,7 +117,7 @@ namespace BitmapHandler { logGlobal->error("Failed to open %s as H3 PCX!", fname); } - return image; + return image.copy(); //copy must be returned here because buffer readFile.first used to build QImage will be cleaned after this line } else { //loading via QImage