/* * The ManaPlus Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-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 . */ #include "resources/sprite/animatedsprite.h" #include "const/resources/spriteaction.h" #include "render/graphics.h" #include "resources/action.h" #include "resources/delayedmanager.h" #include "resources/animation/animation.h" #include "resources/image/image.h" #include "resources/loaders/spritedefloader.h" #include "resources/resourcemanager/resourcemanager.h" #include "resources/sprite/animationdelayload.h" #include "utils/delete2.h" #include "utils/likely.h" #include "utils/mrand.h" #include "debug.h" bool AnimatedSprite::mEnableCache = false; AnimatedSprite::AnimatedSprite(SpriteDef *restrict const sprite) : mDirection(SpriteDirection::DOWN), mLastTime(0), mFrameIndex(0), mFrameTime(0), mSprite(sprite), mAction(nullptr), mAnimation(nullptr), mFrame(nullptr), mNumber(100), mNumber1(100), mDelayLoad(nullptr), mTerminated(false) { mAlpha = 1.0F; // Take possession of the sprite if (mSprite != nullptr) mSprite->incRef(); } AnimatedSprite *AnimatedSprite::load(const std::string &restrict filename, const int variant) { SpriteDef *restrict const s = Loader::getSprite( filename, variant); if (s == nullptr) return nullptr; AnimatedSprite *restrict const as = new AnimatedSprite(s); #ifdef DEBUG_ANIMATIONS as->setSpriteName(filename); #endif // DEBUG_ANIMATIONS as->play(SpriteAction::STAND); s->decRef(); return as; } AnimatedSprite *AnimatedSprite::delayedLoad(const std::string &restrict filename, const int variant) { if (!mEnableCache) return load(filename, variant); Resource *restrict const res = ResourceManager::getFromCache( filename, variant); if (res != nullptr) { res->decRef(); return load(filename, variant); } AnimatedSprite *restrict const as = new AnimatedSprite(nullptr); #ifdef DEBUG_ANIMATIONS as->setSpriteName(filename); #endif // DEBUG_ANIMATIONS as->play(SpriteAction::STAND); as->setDelayLoad(filename, variant); return as; } AnimatedSprite *AnimatedSprite::clone(const AnimatedSprite *restrict const anim) { if (anim == nullptr) return nullptr; AnimatedSprite *restrict const sprite = new AnimatedSprite(anim->mSprite); #ifdef DEBUG_ANIMATIONS sprite->setSpriteName(anim->getSpriteName()); #endif // DEBUG_ANIMATIONS sprite->play(SpriteAction::STAND); return sprite; } AnimatedSprite::~AnimatedSprite() { if (mSprite != nullptr) { mSprite->decRef(); mSprite = nullptr; } if (mDelayLoad != nullptr) { mDelayLoad->clearSprite(); DelayedManager::removeDelayLoad(mDelayLoad); delete2(mDelayLoad) } } bool AnimatedSprite::reset() restrict2 { const bool ret = mFrameIndex !=0 || mFrameTime != 0 || mLastTime != 0; mFrameIndex = 0; mFrameTime = 0; mLastTime = 0; if (mAnimation != nullptr) mFrame = &mAnimation->mFrames[0]; else mFrame = nullptr; return ret; } bool AnimatedSprite::play(const std::string &restrict spriteAction) restrict2 { if (mSprite == nullptr) { if (mDelayLoad == nullptr) return false; mDelayLoad->setAction(spriteAction); return true; } const Action *const action = mSprite->getAction(spriteAction, mNumber); if (action == nullptr) return false; mAction = action; const Animation *const animation = mAction->getAnimation(mDirection); if ((animation != nullptr) && animation != mAnimation && animation->getLength() > 0) { mAnimation = animation; reset(); return true; } return false; } bool AnimatedSprite::update(const int time) restrict2 { // Avoid freaking out at first frame or when tick_time overflows if (A_UNLIKELY(time < mLastTime || mLastTime == 0)) mLastTime = time; // If not enough time has passed yet, do nothing if (time <= mLastTime || (mAnimation == nullptr)) return false; const unsigned int dt = time - mLastTime; mLastTime = time; const Animation *restrict const animation = mAnimation; const Frame *restrict const frame = mFrame; if (A_UNLIKELY(!updateCurrentAnimation(dt))) { // Animation finished, reset to default play(SpriteAction::STAND); mTerminated = true; } // Make sure something actually changed return animation != mAnimation || frame != mFrame; } bool AnimatedSprite::updateCurrentAnimation(const unsigned int time) restrict2 { // move code from Animation::isTerminator(*mFrame) if (mFrame == nullptr || mAnimation == nullptr || (mFrame->image == nullptr && mFrame->type == FrameType::ANIMATION)) { return false; } mFrameTime += time; while ((mFrameTime > CAST_U32(mFrame->delay) && mFrame->delay > 0) || (mFrame->type != FrameType::ANIMATION && mFrame->type != FrameType::PAUSE)) { bool fail(true); mFrameTime -= CAST_U32(mFrame->delay); mFrameIndex++; if (mFrameIndex >= CAST_U32(mAnimation->getLength())) mFrameIndex = 0; mFrame = &mAnimation->mFrames[mFrameIndex]; if (mFrame->type == FrameType::LABEL && !mFrame->nextAction.empty()) { fail = false; } else if (mFrame->type == FrameType::GOTO && !mFrame->nextAction.empty()) { const int rand = mFrame->rand; if (rand == 100 || ((rand != 0) && rand >= mrand() % 100)) { for (size_t i = 0; i < mAnimation->getLength(); i ++) { const Frame *restrict const frame = &mAnimation->mFrames[i]; if (frame->type == FrameType::LABEL && mFrame->nextAction == frame->nextAction) { mFrameIndex = CAST_U32(i); if (mFrameIndex >= CAST_U32( mAnimation->getLength())) { mFrameIndex = 0; } mFrame = &mAnimation->mFrames[mFrameIndex]; fail = false; break; } } } else { fail = false; } } else if (mFrame->type == FrameType::JUMP && !mFrame->nextAction.empty()) { const int rand = mFrame->rand; if (rand == 100 || ((rand != 0) && rand >= mrand() % 100)) { play(mFrame->nextAction); return true; } } // copy code from Animation::isTerminator(*mFrame) else if ((mFrame->image == nullptr) && mFrame->type == FrameType::ANIMATION) { const int rand = mFrame->rand; if (rand == 100 || ((rand != 0) && rand >= mrand() % 100)) { mAnimation = nullptr; mFrame = nullptr; return false; } } else { const int rand = mFrame->rand; if (rand == 100 || mFrameIndex >= CAST_U32(mAnimation->getLength())) { fail = false; } else { if ((rand != 0) && mrand() % 100 <= rand) fail = false; } } if (fail) { if (mFrame != nullptr) mFrameTime = mFrame->delay + 1; else mFrameTime ++; } } return true; } void AnimatedSprite::draw(Graphics *restrict const graphics, const int posX, const int posY) const restrict2 { FUNC_BLOCK("AnimatedSprite::draw", 1) if ((mFrame == nullptr) || (mFrame->image == nullptr)) return; Image *restrict const image = mFrame->image; image->setAlpha(mAlpha); graphics->drawImage(image, posX + mFrame->offsetX, posY + mFrame->offsetY); } void AnimatedSprite::drawRescaled(Graphics *restrict const graphics, const int posX, const int posY, const int dx, const int dy) const restrict2 { if (mFrame == nullptr || mFrame->image == nullptr) { return; } Image *restrict const image = mFrame->image; image->setAlpha(mAlpha); graphics->drawRescaledImage(image, posX + mFrame->offsetX, posY + mFrame->offsetY, dx, dy); } void AnimatedSprite::drawRaw(Graphics *restrict const graphics, const int posX, const int posY) const restrict2 { if ((mFrame == nullptr) || (mFrame->image == nullptr)) return; Image *restrict const image = mFrame->image; image->setAlpha(mAlpha); graphics->drawImage(image, posX, posY); } bool AnimatedSprite::setSpriteDirection(const SpriteDirection::Type direction) restrict2 { if (mDirection != direction) { mDirection = direction; if (mAction == nullptr) return false; const Animation *restrict const animation = mAction->getAnimation(mDirection); if ((animation != nullptr) && animation != mAnimation && animation->getLength() > 0) { mAnimation = animation; reset(); } return true; } return false; } unsigned int AnimatedSprite::getFrameCount() const restrict2 { if (mAnimation != nullptr) return CAST_U32(mAnimation->getLength()); return 0; } int AnimatedSprite::getWidth() const restrict2 { if ((mFrame != nullptr) && (mFrame->image != nullptr)) return mFrame->image->mBounds.w; return 0; } int AnimatedSprite::getHeight() const restrict2 { if ((mFrame != nullptr) && (mFrame->image != nullptr)) return mFrame->image->mBounds.h; return 0; } std::string AnimatedSprite::getIdPath() const restrict2 { if (mSprite == nullptr) return ""; return mSprite->mIdPath; } const Image* AnimatedSprite::getImage() const restrict2 noexcept2 { return mFrame != nullptr ? mFrame->image : nullptr; } void AnimatedSprite::setAlpha(float alpha) restrict2 { mAlpha = alpha; if (mFrame != nullptr) { Image *restrict const image = mFrame->image; if (image != nullptr) image->setAlpha(mAlpha); } } const void *AnimatedSprite::getHash() const restrict2 { if (mFrame != nullptr) return mFrame; return this; } bool AnimatedSprite::updateNumber(const unsigned num) restrict2 { if (mSprite == nullptr) return false; if (mNumber1 != num) { mNumber1 = num; mNumber = mSprite->findNumber(num); if (mNumber == 0U) { mNumber = 100; return false; } return true; } return false; } void AnimatedSprite::setDelayLoad(const std::string &restrict filename, const int variant) restrict2 { if (mDelayLoad != nullptr) { mDelayLoad->clearSprite(); DelayedManager::removeDelayLoad(mDelayLoad); delete mDelayLoad; } mDelayLoad = new AnimationDelayLoad(filename, variant, this); DelayedManager::addDelayedAnimation(mDelayLoad); }