/*
* The ManaPlus Client
* Copyright (C) 2010 The Mana Developers
* Copyright (C) 2011-2020 The ManaPlus Developers
* Copyright (C) 2020-2023 The ManaVerse Developers
*
* This file is part of The ManaPlus Client.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "being/compoundsprite.h"
#include "configuration.h"
#include "sdlshared.h"
#include "being/compounditem.h"
#include "render/surfacegraphics.h"
#if defined(USE_OPENGL) || !defined(USE_SDL2)
#include "resources/imagehelper.h"
#endif // USE_OPENGL
#include "resources/image/image.h"
#include "utils/delete2.h"
#include "utils/dtor.h"
#include "utils/foreach.h"
#include "utils/likely.h"
#include "utils/sdlcheckutils.h"
#ifndef USE_SDL2
#include "game.h"
#include "const/resources/map/map.h"
#include "resources/map/map.h"
#include "utils/timer.h"
PRAGMA48(GCC diagnostic push)
PRAGMA48(GCC diagnostic ignored "-Wshadow")
#ifndef SDL_BIG_ENDIAN
#include <SDL_endian.h>
#endif // SDL_BYTEORDER
PRAGMA48(GCC diagnostic pop)
#endif // USE_SDL2
#include "debug.h"
#ifndef USE_SDL2
static const int BUFFER_WIDTH = 100;
static const int BUFFER_HEIGHT = 100;
static const unsigned cache_max_size = 10;
static const unsigned cache_clean_part = 3;
#endif // USE_SDL2
bool CompoundSprite::mEnableDelay = true;
CompoundSprite::CompoundSprite() :
Sprite(),
mSprites(),
imagesCache(),
mCacheItem(nullptr),
mImage(nullptr),
mAlphaImage(nullptr),
mOffsetX(0),
mOffsetY(0),
mStartTime(0),
mLastTime(0),
#ifndef USE_SDL2
mNextRedrawTime(0),
#endif // USE_SDL2
mNeedsRedraw(false),
mEnableAlphaFix(config.getBoolValue("enableAlphaFix")),
mDisableAdvBeingCaching(config.getBoolValue("disableAdvBeingCaching")),
mDisableBeingCaching(config.getBoolValue("disableBeingCaching"))
{
mAlpha = 1.0F;
}
CompoundSprite::~CompoundSprite()
{
clear();
mImage = nullptr;
mAlphaImage = nullptr;
}
bool CompoundSprite::reset()
{
bool ret = false;
FOR_EACH (SpriteIterator, it, mSprites)
{
if (*it != nullptr)
ret |= (*it)->reset();
}
if (ret)
mLastTime = 0;
mNeedsRedraw |= ret;
return ret;
}
bool CompoundSprite::play(const std::string &action)
{
bool ret = false;
bool ret2 = true;
FOR_EACH (SpriteIterator, it, mSprites)
{
if (*it != nullptr)
{
const bool tmpVal = (*it)->play(action);
ret |= tmpVal;
ret2 &= tmpVal;
}
}
mNeedsRedraw |= ret;
if (ret2)
mLastTime = 0;
return ret;
}
bool CompoundSprite::update(const int time)
{
bool ret = false;
if (A_UNLIKELY(mLastTime == 0))
mStartTime = time;
mLastTime = time;
FOR_EACH (SpriteIterator, it, mSprites)
{
if (*it != nullptr)
ret |= (*it)->update(time);
}
mNeedsRedraw |= ret;
return ret;
}
void CompoundSprite::drawSimple(Graphics *const graphics,
const int posX,
const int posY) const
{
FUNC_BLOCK("CompoundSprite::draw", 1)
if (mNeedsRedraw)
updateImages();
if (mSprites.empty()) // Nothing to draw
return;
if (mAlpha == 1.0F && (mImage != nullptr))
{
graphics->drawImage(mImage, posX + mOffsetX, posY + mOffsetY);
}
else if ((mAlpha != 0.0F) && (mAlphaImage != nullptr))
{
mAlphaImage->setAlpha(mAlpha);
graphics->drawImage(mAlphaImage,
posX + mOffsetX, posY + mOffsetY);
}
else
{
CompoundSprite::drawSprites(graphics, posX, posY);
}
}
void CompoundSprite::drawSprites(Graphics *const graphics,
const int posX,
const int posY) const
{
FOR_EACH (SpriteConstIterator, it, mSprites)
{
if (*it != nullptr)
{
(*it)->setAlpha(mAlpha);
(*it)->draw(graphics, posX, posY);
}
}
}
void CompoundSprite::drawSpritesSDL(Graphics *const graphics,
const int posX,
const int posY) const
{
FOR_EACH (SpriteConstIterator, it, mSprites)
{
if (*it != nullptr)
(*it)->draw(graphics, posX, posY);
}
}
int CompoundSprite::getWidth() const
{
FOR_EACH (SpriteConstIterator, it, mSprites)
{
const Sprite *const base = *it;
if (base != nullptr)
return base->getWidth();
}
return 0;
}
int CompoundSprite::getHeight() const
{
FOR_EACH (SpriteConstIterator, it, mSprites)
{
const Sprite *const base = *it;
if (base != nullptr)
return base->getHeight();
}
return 0;
}
const Image *CompoundSprite::getImage() const
{
return mImage;
}
bool CompoundSprite::setSpriteDirection(const SpriteDirection::Type direction)
{
bool ret = false;
FOR_EACH (SpriteIterator, it, mSprites)
{
if (*it != nullptr)
ret |= (*it)->setSpriteDirection(direction);
}
if (ret)
mLastTime = 0;
mNeedsRedraw |= ret;
return ret;
}
int CompoundSprite::getNumberOfLayers() const
{
if ((mImage != nullptr) || (mAlphaImage != nullptr))
return 1;
return CAST_S32(mSprites.size());
}
unsigned int CompoundSprite::getCurrentFrame() const
{
FOR_EACH (SpriteConstIterator, it, mSprites)
{
if (*it != nullptr)
return (*it)->getCurrentFrame();
}
return 0;
}
unsigned int CompoundSprite::getFrameCount() const
{
FOR_EACH (SpriteConstIterator, it, mSprites)
{
if (*it != nullptr)
return (*it)->getFrameCount();
}
return 0;
}
void CompoundSprite::addSprite(Sprite *const sprite)
{
mSprites.push_back(sprite);
mNeedsRedraw = true;
}
void CompoundSprite::setSprite(const size_t layer, Sprite *const sprite)
{
// Skip if it won't change anything
if (mSprites[layer] == sprite)
return;
delete mSprites[layer];
mSprites[layer] = sprite;
mNeedsRedraw = true;
}
void CompoundSprite::removeSprite(const int layer)
{
// Skip if it won't change anything
if (mSprites[layer] == nullptr)
return;
delete2(mSprites[layer])
mNeedsRedraw = true;
}
void CompoundSprite::clear()
{
// Skip if it won't change anything
if (!mSprites.empty())
{
delete_all(mSprites);
mSprites.clear();
}
mNeedsRedraw = true;
delete_all(imagesCache);
imagesCache.clear();
delete2(mCacheItem)
mLastTime = 0;
}
void CompoundSprite::ensureSize(const size_t layerCount)
{
// Skip if it won't change anything
if (mSprites.size() >= layerCount)
return;
// resize(layerCount, nullptr);
mSprites.resize(layerCount);
}
void CompoundSprite::redraw() const
{
#ifndef USE_SDL2
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
const uint32_t rmask = 0xff000000U;
const uint32_t gmask = 0x00ff0000U;
const uint32_t bmask = 0x0000ff00U;
const uint32_t amask = 0x000000ffU;
#else // SDL_BYTEORDER == SDL_BIG_ENDIAN
const uint32_t rmask = 0x000000ffU;
const uint32_t gmask = 0x0000ff00U;
const uint32_t bmask = 0x00ff0000U;
const uint32_t amask = 0xff000000U;
#endif // SDL_BYTEORDER == SDL_BIG_ENDIAN
SDL_Surface *const surface = MSDL_CreateRGBSurface(SDL_HWSURFACE,
BUFFER_WIDTH, BUFFER_HEIGHT, 32, rmask, gmask, bmask, amask);
if (surface == nullptr)
return;
SurfaceGraphics *graphics = new SurfaceGraphics;
graphics->setBlitMode(BlitMode::BLIT_GFX);
graphics->setTarget(surface);
graphics->beginDraw();
int tileX = mapTileSize / 2;
int tileY = mapTileSize;
const Game *const game = Game::instance();
if (game != nullptr)
{
const Map *const map = game->getCurrentMap();
if (map != nullptr)
{
tileX = map->getTileWidth() / 2;
tileY = map->getTileWidth();
}
}
const int posX = BUFFER_WIDTH / 2 - tileX;
const int posY = BUFFER_HEIGHT - tileY;
mOffsetX = tileX - BUFFER_WIDTH / 2;
mOffsetY = tileY - BUFFER_HEIGHT;
drawSpritesSDL(graphics, posX, posY);
delete2(graphics)
SDL_SetAlpha(surface, 0, SDL_ALPHA_OPAQUE);
delete mAlphaImage;
if (ImageHelper::mEnableAlpha)
{
SDL_Surface *const surfaceA = MSDL_CreateRGBSurface(SDL_HWSURFACE,
BUFFER_WIDTH, BUFFER_HEIGHT, 32, rmask, gmask, bmask, amask);
SDL_BlitSurface(surface, nullptr, surfaceA, nullptr);
mAlphaImage = imageHelper->loadSurface(surfaceA);
MSDL_FreeSurface(surfaceA);
}
else
{
mAlphaImage = nullptr;
}
delete mImage;
mImage = imageHelper->loadSurface(surface);
MSDL_FreeSurface(surface);
#endif // USE_SDL2
}
void CompoundSprite::setAlpha(float alpha)
{
if (alpha != mAlpha)
{
if (mEnableAlphaFix &&
#ifdef USE_OPENGL
imageHelper->useOpenGL() == RENDER_SOFTWARE &&
#endif // USE_OPENGL
mSprites.size() > 3U)
{
FOR_EACH (SpriteConstIterator, it, mSprites)
{
if (*it != nullptr)
(*it)->setAlpha(alpha);
}
}
mAlpha = alpha;
}
}
void CompoundSprite::updateImages() const
{
#ifndef USE_SDL2
#ifdef USE_OPENGL
if (imageHelper->useOpenGL() != RENDER_SOFTWARE)
return;
#endif // USE_OPENGL
if (mEnableDelay)
{
if (get_elapsed_time1(mNextRedrawTime) < 10)
return;
mNextRedrawTime = tick_time;
}
mNeedsRedraw = false;
if (!mDisableBeingCaching)
{
if (mSprites.size() <= 3U)
return;
if (!mDisableAdvBeingCaching)
{
if (updateFromCache())
return;
redraw();
if (mImage != nullptr)
initCurrentCacheItem();
}
else
{
redraw();
}
}
#endif // USE_SDL2
}
bool CompoundSprite::updateFromCache() const
{
#ifndef USE_SDL2
// static int hits = 0;
// static int miss = 0;
if ((mCacheItem != nullptr) && (mCacheItem->image != nullptr))
{
imagesCache.push_front(mCacheItem);
mCacheItem = nullptr;
if (imagesCache.size() > cache_max_size)
{
for (unsigned f = 0; f < cache_clean_part; f ++)
{
CompoundItem *item = imagesCache.back();
imagesCache.pop_back();
delete item;
}
}
}
// logger->log("cache size: %d, hit %d, miss %d",
// (int)imagesCache.size(), hits, miss);
const size_t sz = mSprites.size();
FOR_EACH (ImagesCache::iterator, it, imagesCache)
{
CompoundItem *const ic = *it;
if ((ic != nullptr) && ic->data.size() == sz)
{
bool fail(false);
VectorPointers::const_iterator it2 = ic->data.begin();
const VectorPointers::const_iterator it2_end = ic->data.end();
for (SpriteConstIterator it1 = mSprites.begin(),
it1_end = mSprites.end();
it1 != it1_end && it2 != it2_end;
++ it1, ++ it2)
{
const void *ptr1 = nullptr;
const void *ptr2 = nullptr;
if (*it1 != nullptr)
ptr1 = (*it1)->getHash();
if (*it2 != nullptr)
ptr2 = *it2;
if (ptr1 != ptr2)
{
fail = true;
break;
}
}
if (!fail)
{
// hits ++;
mImage = (*it)->image;
mAlphaImage = (*it)->alphaImage;
imagesCache.erase(it);
mCacheItem = ic;
return true;
}
}
}
mImage = nullptr;
mAlphaImage = nullptr;
// miss++;
#endif // USE_SDL2
return false;
}
void CompoundSprite::initCurrentCacheItem() const
{
delete mCacheItem;
mCacheItem = new CompoundItem;
mCacheItem->image = mImage;
mCacheItem->alphaImage = mAlphaImage;
// mCacheItem->alpha = mAlpha;
FOR_EACH (SpriteConstIterator, it, mSprites)
{
if (*it != nullptr)
mCacheItem->data.push_back((*it)->getHash());
else
mCacheItem->data.push_back(nullptr);
}
}
bool CompoundSprite::updateNumber(const unsigned num)
{
bool res(false);
FOR_EACH (SpriteConstIterator, it, mSprites)
{
if (*it != nullptr)
{
if ((*it)->updateNumber(num))
res = true;
}
}
return res;
}
CompoundItem::CompoundItem() :
data(),
image(nullptr),
alphaImage(nullptr)
{
}
CompoundItem::~CompoundItem()
{
delete image;
delete alphaImage;
}