diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index 55ca1aa5e..1e82f7e44 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -589,6 +589,42 @@ ResurrectionAnimation::ResurrectionAnimation(BattleInterface & owner, const CSta } +bool FadingAnimation::init() +{ + logAnim->info("FadingAnimation::init: stack %s", stack->getName()); + //TODO: pause animation? + return true; +} + +void FadingAnimation::nextFrame() +{ + float elapsed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f; + float fullTime = AnimationControls::getFadeInDuration(); + float delta = elapsed / fullTime; + progress += delta; + + if (progress > 1.0f) + progress = 1.0f; + + uint8_t blueFactor = stack->cloned ? 0 : 255; + uint8_t blueAdded = stack->cloned ? 255 : 0; + uint8_t alpha = CSDL_Ext::lerp(from, dest, progress); + + ColorShifterMultiplyAndAdd shifterFade ({255, 255, blueFactor, alpha}, {0, 0, blueAdded, 0}); + stackAnimation(stack)->shiftColor(&shifterFade); + + if (progress == 1.0f) + delete this; +} + +FadingAnimation::FadingAnimation(BattleInterface & owner, const CStack * _stack, uint8_t alphaFrom, uint8_t alphaDest): + BattleStackAnimation(owner, _stack), + from(alphaFrom), + dest(alphaDest) +{ +} + + RangedAttackAnimation::RangedAttackAnimation(BattleInterface & owner_, const CStack * attacker, BattleHex dest_, const CStack * defender) : AttackAnimation(owner_, attacker, dest_, defender), projectileEmitted(false) @@ -707,7 +743,6 @@ void RangedAttackAnimation::nextFrame() RangedAttackAnimation::~RangedAttackAnimation() { - //FIXME: this assert triggers under some unclear, rare conditions. Possibly - if game window is inactive and/or in foreground/minimized? assert(!owner.projectilesController->hasActiveProjectile(attackingStack)); assert(projectileEmitted); @@ -846,7 +881,7 @@ void CastAnimation::createProjectile(const Point & from, const Point & dest) con uint32_t CastAnimation::getAttackClimaxFrame() const { - //FIXME: allow defining this parameter in config file, separately from attackClimaxFrame of missile attacks + //TODO: allow defining this parameter in config file, separately from attackClimaxFrame of missile attacks uint32_t maxFrames = stackAnimation(attackingStack)->framesInGroup(group); if (maxFrames > 2) @@ -1117,7 +1152,7 @@ void HeroCastAnimation::nextFrame() { float frame = hero->getFrame(); - if (frame < 4.0f) + if (frame < 4.0f) // middle point of animation //TODO: un-hardcode return; if (!projectileEmitted) @@ -1130,7 +1165,7 @@ void HeroCastAnimation::nextFrame() if (!owner.projectilesController->hasActiveProjectile(nullptr)) { emitAnimationEvent(); - //FIXME: check H3 - it is possible that hero animation should be paused until hit effect is over, not just projectile + //TODO: check H3 - it is possible that hero animation should be paused until hit effect is over, not just projectile hero->play(); } } diff --git a/client/battle/BattleAnimationClasses.h b/client/battle/BattleAnimationClasses.h index cc21a1430..f5a73ce2a 100644 --- a/client/battle/BattleAnimationClasses.h +++ b/client/battle/BattleAnimationClasses.h @@ -205,9 +205,21 @@ public: ResurrectionAnimation(BattleInterface & owner, const CStack * _stack); }; +/// Performs fade-in or fade-out animation on stack +class FadingAnimation : public BattleStackAnimation +{ + float progress; + uint8_t from; + uint8_t dest; +public: + bool init() override; + void nextFrame() override; + + FadingAnimation(BattleInterface & owner, const CStack * _stack, uint8_t alphaFrom, uint8_t alphaDest); +}; + class RangedAttackAnimation : public AttackAnimation { - void setAnimationGroup(); void initializeProjectile(); void emitProjectile(); diff --git a/client/battle/BattleConstants.h b/client/battle/BattleConstants.h index acfa5eed8..04f74b1b6 100644 --- a/client/battle/BattleConstants.h +++ b/client/battle/BattleConstants.h @@ -62,7 +62,7 @@ enum Type // list of creature animations, numbers were taken from def files HITTED = 3, // base animation for when stack is taking damage DEFENCE = 4, // alternative animation for defending in melee if stack spent its action on defending DEATH = 5, - DEATH_RANGED = 6, // alternative animation for when stack is killed by ranged attack + DEATH_RANGED = 6, // Optional, alternative animation for when stack is killed by ranged attack TURN_L = 7, TURN_R = 8, //TURN_L2 = 9, //unused - identical to TURN_L @@ -70,14 +70,14 @@ enum Type // list of creature animations, numbers were taken from def files ATTACK_UP = 11, ATTACK_FRONT = 12, ATTACK_DOWN = 13, - SHOOT_UP = 14, - SHOOT_FRONT = 15, - SHOOT_DOWN = 16, - CAST_UP = 17, - CAST_FRONT = 18, - CAST_DOWN = 19, - MOVE_START = 20, // small animation to be played before MOVING - MOVE_END = 21, // small animation to be played after MOVING + SHOOT_UP = 14, // Shooters only + SHOOT_FRONT = 15, // Shooters only + SHOOT_DOWN = 16, // Shooters only + CAST_UP = 17, // If empty, fallback to CAST_FRONT + CAST_FRONT = 18, // Used for any special moves - dragon breath, spellcasting, (possibly - Pit Lord/Ogre Mage ability) + CAST_DOWN = 19, // If empty, fallback to CAST_FRONT + MOVE_START = 20, // small animation to be played before MOVING + MOVE_END = 21, // small animation to be played after MOVING DEAD = 22, // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here DEAD_RANGED = 23, // new group, used to show dead stacks (if DEATH_RANGED was used). If empty - last frame from "DEATH_RANGED" will be copied here diff --git a/client/battle/BattleEffectsController.cpp b/client/battle/BattleEffectsController.cpp index 396996493..dc1881ec7 100644 --- a/client/battle/BattleEffectsController.cpp +++ b/client/battle/BattleEffectsController.cpp @@ -73,7 +73,6 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt //don't show animation when no HP is regenerated switch(bte.effect) { - //TODO: move to bonus type handler case Bonus::HP_REGENERATION: displayEffect(EBattleEffect::REGENERATION, soundBase::REGENER, stack->getPosition()); break; diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index e46573949..167e97d9f 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -318,7 +318,7 @@ void BattleInterface::stackReset(const CStack * stack) void BattleInterface::stackAdded(const CStack * stack) { - stacksController->stackAdded(stack); + stacksController->stackAdded(stack, false); } void BattleInterface::stackRemoved(uint32_t stackID) @@ -328,7 +328,7 @@ void BattleInterface::stackRemoved(uint32_t stackID) queue->update(); } -void BattleInterface::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities +void BattleInterface::stackActivated(const CStack *stack) { stacksController->stackActivated(stack); } diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 43703c893..191f3186e 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -89,7 +89,7 @@ BattleStacksController::BattleStacksController(BattleInterface & owner): std::vector stacks = owner.curInt->cb->battleGetAllStacks(true); for(const CStack * s : stacks) { - stackAdded(s); + stackAdded(s, true); } } @@ -172,17 +172,15 @@ void BattleStacksController::stackReset(const CStack * stack) }); } - static const ColorShifterMultiplyAndAdd shifterClone ({255, 255, 0, 255}, {0, 0, 255, 0}); - - if (stack->isClone()) - { - animation->shiftColor(&shifterClone); - } - + //static const ColorShifterMultiplyAndAdd shifterClone ({255, 255, 0, 255}, {0, 0, 255, 0}); + //if (stack->isClone()) + //{ + // animation->shiftColor(&shifterClone); + //} //owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); } -void BattleStacksController::stackAdded(const CStack * stack) +void BattleStacksController::stackAdded(const CStack * stack, bool instant) { // Tower shooters have only their upper half visible static const int turretCreatureAnimationHeight = 235; @@ -212,6 +210,17 @@ void BattleStacksController::stackAdded(const CStack * stack) stackAnimation[stack->ID]->pos.y = coords.y; stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth(); stackAnimation[stack->ID]->setType(ECreatureAnimType::HOLDING); + + if (!instant) + { + ColorShifterMultiplyAndAdd shifterFade ({255, 255, 255, 0}, {0, 0, 0, 0}); + stackAnimation[stack->ID]->shiftColor(&shifterFade); + + owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]() + { + addNewAnim(new FadingAnimation(owner, stack, 0, 255)); + }); + } } void BattleStacksController::setActiveStack(const CStack *stack) diff --git a/client/battle/BattleStacksController.h b/client/battle/BattleStacksController.h index 8db792208..a70e2a5fc 100644 --- a/client/battle/BattleStacksController.h +++ b/client/battle/BattleStacksController.h @@ -86,7 +86,7 @@ public: bool facingRight(const CStack * stack) const; void stackReset(const CStack * stack); - void stackAdded(const CStack * stack); //new stack appeared on battlefield + void stackAdded(const CStack * stack, bool instant); //new stack appeared on battlefield void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled void stackActivated(const CStack *stack); //active stack has been changed void stackMoved(const CStack *stack, std::vector destHex, int distance); //stack with id number moved to destHex diff --git a/client/battle/CreatureAnimation.cpp b/client/battle/CreatureAnimation.cpp index 2167c30a1..60fdd5170 100644 --- a/client/battle/CreatureAnimation.cpp +++ b/client/battle/CreatureAnimation.cpp @@ -19,6 +19,11 @@ static const SDL_Color creatureBlueBorder = { 0, 255, 255, 255 }; static const SDL_Color creatureGoldBorder = { 255, 255, 0, 255 }; static const SDL_Color creatureNoBorder = { 0, 0, 0, 0 }; +static SDL_Color genShadow(ui8 alpha) +{ + return CSDL_Ext::makeColor(0, 0, 0, alpha); +} + SDL_Color AnimationControls::getBlueBorder() { return creatureBlueBorder; @@ -134,6 +139,11 @@ float AnimationControls::getFlightDistance(const CCreature * creature) return static_cast(creature->animation.flightAnimationDistance * 200); } +float AnimationControls::getFadeInDuration() +{ + return 1.0f / settings["battle"]["animationSpeed"].Float(); +} + ECreatureAnimType::Type CreatureAnimation::getType() const { return type; @@ -150,6 +160,10 @@ void CreatureAnimation::setType(ECreatureAnimType::Type type) void CreatureAnimation::shiftColor(const ColorShifter* shifter) { + SDL_Color shadowTest = shifter->shiftColor(genShadow(128)); + + shadowAlpha = shadowTest.a; + if(forward) forward->shiftColor(shifter); @@ -160,6 +174,7 @@ void CreatureAnimation::shiftColor(const ColorShifter* shifter) CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController controller) : name(name_), speed(0.1f), + shadowAlpha(128), currentFrame(0), elapsedTime(0), type(ECreatureAnimType::HOLDING), @@ -281,11 +296,6 @@ inline int getBorderStrength(float time) return static_cast(borderStrength * 155 + 100); // scale to 0-255 } -static SDL_Color genShadow(ui8 alpha) -{ - return CSDL_Ext::makeColor(0, 0, 0, alpha); -} - static SDL_Color genBorderColor(ui8 alpha, const SDL_Color & base) { return CSDL_Ext::makeColor(base.r, base.g, base.b, ui8(base.a * alpha / 256)); @@ -306,11 +316,16 @@ static SDL_Color addColors(const SDL_Color & base, const SDL_Color & over) ); } -void CreatureAnimation::genBorderPalette(IImage::BorderPallete & target) +void CreatureAnimation::genSpecialPalette(IImage::SpecialPalette & target) { target[0] = genBorderColor(getBorderStrength(elapsedTime), border); - target[1] = addColors(genShadow(128), genBorderColor(getBorderStrength(elapsedTime), border)); - target[2] = addColors(genShadow(64), genBorderColor(getBorderStrength(elapsedTime), border)); + target[1] = genShadow(shadowAlpha / 2); + target[2] = genShadow(shadowAlpha / 2); + target[3] = genShadow(shadowAlpha); + target[4] = genShadow(shadowAlpha); + target[5] = genBorderColor(getBorderStrength(elapsedTime), border); + target[6] = addColors(genShadow(shadowAlpha), genBorderColor(getBorderStrength(elapsedTime), border)); + target[7] = addColors(genShadow(shadowAlpha / 2), genBorderColor(getBorderStrength(elapsedTime), border)); } void CreatureAnimation::nextFrame(Canvas & canvas, bool facingRight) @@ -326,12 +341,13 @@ void CreatureAnimation::nextFrame(Canvas & canvas, bool facingRight) if(image) { - IImage::BorderPallete borderPallete; - genBorderPalette(borderPallete); + IImage::SpecialPalette SpecialPalette; + genSpecialPalette(SpecialPalette); - image->setBorderPallete(borderPallete); + image->setSpecialPallete(SpecialPalette); canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h)); + } } diff --git a/client/battle/CreatureAnimation.h b/client/battle/CreatureAnimation.h index ca22fa7bf..7e9d195fc 100644 --- a/client/battle/CreatureAnimation.h +++ b/client/battle/CreatureAnimation.h @@ -46,6 +46,9 @@ namespace AnimationControls /// Returns distance on which flying creatures should during one animation loop float getFlightDistance(const CCreature * creature); + + /// Returns total time for full fade-in effect on newly summoned creatures, in seconds + float getFadeInDuration(); } /// Class which manages animations of creatures/units inside battles @@ -80,6 +83,9 @@ private: ///type of animation being displayed ECreatureAnimType::Type type; + /// current value of shadow transparency + uint8_t shadowAlpha; + /// border color, disabled if alpha = 0 SDL_Color border; @@ -90,7 +96,7 @@ private: void endAnimation(); - void genBorderPalette(IImage::BorderPallete & target); + void genSpecialPalette(IImage::SpecialPalette & target); public: /// function(s) that will be called when animation ends, after reset to 1st frame diff --git a/client/gui/CAnimation.cpp b/client/gui/CAnimation.cpp index 42cf6916b..73f5b4a23 100644 --- a/client/gui/CAnimation.cpp +++ b/client/gui/CAnimation.cpp @@ -108,7 +108,7 @@ public: void adjustPalette(const ColorShifter * shifter) override; void resetPalette() override; - void setBorderPallete(const BorderPallete & borderPallete) override; + void setSpecialPallete(const SpecialPalette & SpecialPalette) override; friend class SDLImageLoader; @@ -212,32 +212,17 @@ CDefFile::CDefFile(std::string Name): data(nullptr), palette(nullptr) { - - #if 0 - static SDL_Color H3_ORIG_PALETTE[8] = - { - { 0, 255, 255, SDL_ALPHA_OPAQUE}, - {255, 150, 255, SDL_ALPHA_OPAQUE}, - {255, 100, 255, SDL_ALPHA_OPAQUE}, - {255, 50, 255, SDL_ALPHA_OPAQUE}, - {255, 0, 255, SDL_ALPHA_OPAQUE}, - {255, 255, 0, SDL_ALPHA_OPAQUE}, - {180, 0, 255, SDL_ALPHA_OPAQUE}, - { 0, 255, 0, SDL_ALPHA_OPAQUE} - }; - #endif // 0 - //First 8 colors in def palette used for transparency static SDL_Color H3Palette[8] = { - { 0, 0, 0, 0},// 100% - transparency - { 0, 0, 0, 32},// 75% - shadow border, - { 0, 0, 0, 64},// TODO: find exact value - { 0, 0, 0, 128},// TODO: for transparency - { 0, 0, 0, 128},// 50% - shadow body - { 0, 0, 0, 0},// 100% - selection highlight - { 0, 0, 0, 128},// 50% - shadow body below selection - { 0, 0, 0, 64} // 75% - shadow border below selection + { 0, 0, 0, 0},// transparency ( used in most images ) + { 0, 0, 0, 64},// shadow border ( used in battle, adventure map def's ) + { 0, 0, 0, 64},// shadow border ( used in fog-of-war def's ) + { 0, 0, 0, 128},// shadow body ( used in fog-of-war def's ) + { 0, 0, 0, 128},// shadow body ( used in battle, adventure map def's ) + { 0, 0, 0, 0},// selection ( used in battle def's ) + { 0, 0, 0, 128},// shadow body below selection ( used in battle def's ) + { 0, 0, 0, 64} // shadow border below selection ( used in battle def's ) }; data = animationCache.getCachedFile(ResourceID(std::string("SPRITES/") + Name, EResType::ANIMATION)); @@ -827,11 +812,11 @@ void SDLImage::resetPalette() SDL_SetPaletteColors(surf->format->palette, originalPalette->colors, 0, originalPalette->ncolors); } -void SDLImage::setBorderPallete(const IImage::BorderPallete & borderPallete) +void SDLImage::setSpecialPallete(const IImage::SpecialPalette & SpecialPalette) { if(surf->format->palette) { - SDL_SetColors(surf, const_cast(borderPallete.data()), 5, 3); + SDL_SetColors(surf, const_cast(SpecialPalette.data()), 1, 7); } } diff --git a/client/gui/CAnimation.h b/client/gui/CAnimation.h index c88f4e2a9..166c8c270 100644 --- a/client/gui/CAnimation.h +++ b/client/gui/CAnimation.h @@ -37,7 +37,7 @@ class ColorShifter; class IImage { public: - using BorderPallete = std::array; + using SpecialPalette = std::array; //draws image on surface "where" at position virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr, ui8 alpha = 255) const=0; @@ -65,8 +65,8 @@ public: virtual void adjustPalette(const ColorShifter * shifter) = 0; virtual void resetPalette() = 0; - //only indexed bitmaps, colors 5,6,7 must be special - virtual void setBorderPallete(const BorderPallete & borderPallete) = 0; + //only indexed bitmaps with 7 special colors + virtual void setSpecialPallete(const SpecialPalette & SpecialPalette) = 0; virtual void horizontalFlip() = 0; virtual void verticalFlip() = 0; diff --git a/client/gui/Canvas.cpp b/client/gui/Canvas.cpp index 8a643a1db..c1e46b0b8 100644 --- a/client/gui/Canvas.cpp +++ b/client/gui/Canvas.cpp @@ -52,6 +52,13 @@ void Canvas::draw(std::shared_ptr image, const Point & pos, const Rect & image->draw(surface, pos.x, pos.y, &sourceRect); } +void Canvas::draw(std::shared_ptr image, const Point & pos, const Rect & sourceRect, uint8_t alpha) +{ + assert(image); + if (image) + image->draw(surface, pos.x, pos.y, &sourceRect, alpha); +} + void Canvas::draw(Canvas & image, const Point & pos) { blitAt(image.surface, pos.x, pos.y, surface); diff --git a/client/gui/Canvas.h b/client/gui/Canvas.h index 46c24c87b..7eae6a041 100644 --- a/client/gui/Canvas.h +++ b/client/gui/Canvas.h @@ -41,6 +41,10 @@ public: /// renders section of image bounded by sourceRect at specified position void draw(std::shared_ptr image, const Point & pos, const Rect & sourceRect); + /// renders section of image bounded by sourceRect at specified position at specific transparency value + void draw(std::shared_ptr image, const Point & pos, const Rect & sourceRect, uint8_t alpha); + + /// renders another canvas onto this canvas void draw(Canvas & image, const Point & pos); diff --git a/config/spells/other.json b/config/spells/other.json index bf2c5dc3c..24a2ed537 100644 --- a/config/spells/other.json +++ b/config/spells/other.json @@ -592,7 +592,6 @@ "index" : 65, "targetType" : "CREATURE", "animation":{ - "cast":[2] }, "sounds": { "cast": "CLONE" @@ -636,7 +635,6 @@ "index" : 66, "targetType" : "NO_TARGET", "animation":{ - "cast":[2] }, "sounds": { "cast": "SUMNELM" @@ -662,7 +660,6 @@ "index" : 67, "targetType" : "NO_TARGET", "animation":{ - "cast":[2] }, "sounds": { "cast": "SUMNELM" @@ -688,7 +685,6 @@ "index" : 68, "targetType" : "NO_TARGET", "animation":{ - "cast":[2] }, "sounds": { "cast": "SUMNELM" @@ -714,7 +710,6 @@ "index" : 69, "targetType" : "NO_TARGET", "animation":{ - "cast":[2] }, "sounds": { "cast": "SUMNELM"