From 270e1b75ceaef05bafea9a40d0baff83013c0ca0 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Fri, 8 Sep 2017 14:25:12 +0300 Subject: [PATCH] Support for creature 2-hex attack, alternate death and cast animation types * margin option .json animation * Use ranged attack animation for spell cast if there is no cast animation, display cast animation only on active casting. --- client/CBitmapHandler.cpp | 2 +- client/battle/CBattleAnimations.cpp | 218 +++++++++++++++++++++++--- client/battle/CBattleAnimations.h | 22 ++- client/battle/CBattleInterface.cpp | 37 +++-- client/battle/CBattleInterface.h | 1 + client/battle/CCreatureAnimation.cpp | 38 +++-- client/battle/CCreatureAnimation.h | 2 +- client/gui/CAnimation.cpp | 166 ++++++++++++-------- client/gui/CAnimation.h | 10 +- client/gui/SDL_Extensions.h | 2 + client/widgets/Images.h | 12 +- lib/NetPacks.h | 3 + lib/spells/CDefaultSpellMechanics.cpp | 4 + 13 files changed, 390 insertions(+), 127 deletions(-) diff --git a/client/CBitmapHandler.cpp b/client/CBitmapHandler.cpp index 94096da33..518a49206 100644 --- a/client/CBitmapHandler.cpp +++ b/client/CBitmapHandler.cpp @@ -178,7 +178,7 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna SDL_Surface * BitmapHandler::loadBitmap(std::string fname, bool setKey) { - SDL_Surface *bitmap; + SDL_Surface * bitmap = nullptr; if (!(bitmap = loadBitmapFromDir("DATA/", fname, setKey)) && !(bitmap = loadBitmapFromDir("SPRITES/", fname, setKey))) diff --git a/client/battle/CBattleAnimations.cpp b/client/battle/CBattleAnimations.cpp index 6aec07ab5..19582e945 100644 --- a/client/battle/CBattleAnimations.cpp +++ b/client/battle/CBattleAnimations.cpp @@ -241,7 +241,12 @@ std::string CDefenceAnimation::getMySound() CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType() { if(killed) - return CCreatureAnim::DEATH; + { + if(rangedAttack && myAnim->framesInGroup(CCreatureAnim::DEATH_RANGED) > 0) + return CCreatureAnim::DEATH_RANGED; + else + return CCreatureAnim::DEATH; + } if(vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM)) return CCreatureAnim::DEFENCE; @@ -272,7 +277,10 @@ void CDefenceAnimation::endAnim() { if(killed) { - myAnim->setType(CCreatureAnim::DEAD); + if(rangedAttack && myAnim->framesInGroup(CCreatureAnim::DEAD_RANGED) > 0) + myAnim->setType(CCreatureAnim::DEAD_RANGED); + else + myAnim->setType(CCreatureAnim::DEAD); } else { @@ -312,26 +320,25 @@ void CDummyAnimation::endAnim() bool CMeleeAttackAnimation::init() { - if( !CAttackAnimation::checkInitialConditions() ) + if(!CAttackAnimation::checkInitialConditions()) return false; if(!attackingStack || myAnim->isDead()) { endAnim(); - return false; } bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->position, owner->creDir[stack->ID], attackedStack->doubleWide(), owner->creDir[attackedStack->ID]); - if (toReverse) + if(toReverse) { owner->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn, true)); return false; } // opponent must face attacker ( = different directions) before he can be attacked - if (attackingStack && attackedStack && + if(attackingStack && attackedStack && owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID]) return false; @@ -339,8 +346,25 @@ bool CMeleeAttackAnimation::init() shooting = false; - static const CCreatureAnim::EAnimType mutPosToGroup[] = {CCreatureAnim::ATTACK_UP, CCreatureAnim::ATTACK_UP, - CCreatureAnim::ATTACK_FRONT, CCreatureAnim::ATTACK_DOWN, CCreatureAnim::ATTACK_DOWN, CCreatureAnim::ATTACK_FRONT}; + static const CCreatureAnim::EAnimType mutPosToGroup[] = + { + CCreatureAnim::ATTACK_UP, + CCreatureAnim::ATTACK_UP, + CCreatureAnim::ATTACK_FRONT, + CCreatureAnim::ATTACK_DOWN, + CCreatureAnim::ATTACK_DOWN, + CCreatureAnim::ATTACK_FRONT + }; + + static const CCreatureAnim::EAnimType mutPosToGroup2H[] = + { + CCreatureAnim::VCMI_2HEX_UP, + CCreatureAnim::VCMI_2HEX_UP, + CCreatureAnim::VCMI_2HEX_FRONT, + CCreatureAnim::VCMI_2HEX_DOWN, + CCreatureAnim::VCMI_2HEX_DOWN, + CCreatureAnim::VCMI_2HEX_FRONT + }; int revShiftattacker = (attackingStack->side == BattleSide::ATTACKER ? -1 : 1); @@ -361,8 +385,20 @@ bool CMeleeAttackAnimation::init() switch(mutPos) //attack direction { - case 0: case 1: case 2: case 3: case 4: case 5: + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: group = mutPosToGroup[mutPos]; + if(attackingStack->hasBonusOfType(Bonus::TWO_HEX_ATTACK_BREATH)) + { + CCreatureAnim::EAnimType group2H = mutPosToGroup2H[mutPos]; + if(myAnim->framesInGroup(group2H)>0) + group = group2H; + } + break; default: logGlobal->error("Critical Error! Wrong dest in stackAttacking! dest: %d; attacking stack pos: %d; mutual pos: %d", dest.hex, attackingStackPosBeforeReturn, mutPos); @@ -672,8 +708,16 @@ void CReverseAnimation::setupSecondPart() endAnim(); } +CRangedAttackAnimation::CRangedAttackAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender) + : CAttackAnimation(owner_, attacker, dest_, defender) +{ + +} + + CShootingAnimation::CShootingAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool _catapult, int _catapultDmg) -: CAttackAnimation(_owner, attacker, _dest, _attacked), catapultDamage(_catapultDmg) + : CRangedAttackAnimation(_owner, attacker, _dest, _attacked), + catapultDamage(_catapultDmg) { logAnim->debug("Created shooting anim for %s", stack->getName()); } @@ -835,9 +879,9 @@ bool CShootingAnimation::init() shooting = true; - if(projectileAngle > straightAngle) //upper shot + if(projectileAngle > straightAngle) group = CCreatureAnim::SHOOT_UP; - else if(projectileAngle < -straightAngle) //lower shot + else if(projectileAngle < -straightAngle) group = CCreatureAnim::SHOOT_DOWN; else //straight shot group = CCreatureAnim::SHOOT_FRONT; @@ -860,23 +904,149 @@ void CShootingAnimation::nextFrame() void CShootingAnimation::endAnim() { - // play wall hit/miss sound for catapult attack - if(!attackedStack) - { - if(catapultDamage > 0) - { - CCS->soundh->playSound("WALLHIT"); - } - else - { - CCS->soundh->playSound("WALLMISS"); - } - } + // play wall hit/miss sound for catapult attack + if(!attackedStack) + { + if(catapultDamage > 0) + { + CCS->soundh->playSound("WALLHIT"); + } + else + { + CCS->soundh->playSound("WALLMISS"); + } + } CAttackAnimation::endAnim(); delete this; } +CCastAnimation::CCastAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender) + : CRangedAttackAnimation(owner_, attacker, dest_, defender) +{ + if(!dest_.isValid() && defender) + dest = defender->position; +} + +bool CCastAnimation::init() +{ + if(!CAttackAnimation::checkInitialConditions()) + return false; + + if(!attackingStack || myAnim->isDead()) + { + endAnim(); + return false; + } + + //reverse unit if necessary + if(attackedStack) + { + if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, attackedStack->position, owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID])) + { + owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true)); + return false; + } + } + else + { + if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, dest, owner->creDir[attackingStack->ID], false, false)) + { + owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true)); + return false; + } + } + + //TODO: display spell projectile here + + static const double straightAngle = 0.2; + + + Point fromPos; + Point destPos; + + // NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise + fromPos = owner->creAnims[attackingStack->ID]->pos.topLeft(); + //xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner); + + destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner); + + + double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x)); + if(attackingStack->position < dest) + projectileAngle = -projectileAngle; + + + if(projectileAngle > straightAngle) + group = CCreatureAnim::VCMI_CAST_UP; + else if(projectileAngle < -straightAngle) + group = CCreatureAnim::VCMI_CAST_DOWN; + else + group = CCreatureAnim::VCMI_CAST_FRONT; + + //fall back to H3 cast/2hex + //even if creature have 2hex attack instead of cast it is ok since we fall back to attack anyway + if(myAnim->framesInGroup(group) == 0) + { + if(projectileAngle > straightAngle) + group = CCreatureAnim::CAST_UP; + else if(projectileAngle < -straightAngle) + group = CCreatureAnim::CAST_DOWN; + else + group = CCreatureAnim::CAST_FRONT; + } + + //fall back to ranged attack + if(myAnim->framesInGroup(group) == 0) + { + if(projectileAngle > straightAngle) + group = CCreatureAnim::SHOOT_UP; + else if(projectileAngle < -straightAngle) + group = CCreatureAnim::SHOOT_DOWN; + else + group = CCreatureAnim::SHOOT_FRONT; + } + + //fall back to normal attack + if(myAnim->framesInGroup(group) == 0) + { + if(projectileAngle > straightAngle) + group = CCreatureAnim::ATTACK_UP; + else if(projectileAngle < -straightAngle) + group = CCreatureAnim::ATTACK_DOWN; + else + group = CCreatureAnim::ATTACK_FRONT; + } + + return true; +} + +void CCastAnimation::nextFrame() +{ + for(auto & it : owner->pendingAnims) + { + CReverseAnimation * anim = dynamic_cast(it.first); + if(anim && anim->stack->ID == stack->ID && anim->priority) + return; + } + + if(myAnim->getType() != group) + { + myAnim->setType(group); + myAnim->onAnimationReset += std::bind(&CAttackAnimation::endAnim, this); + } + + CBattleAnimation::nextFrame(); +} + + +void CCastAnimation::endAnim() +{ + CAttackAnimation::endAnim(); + delete this; +} + + CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom) : CBattleAnimation(_owner), destTile(BattleHex::INVALID), diff --git a/client/battle/CBattleAnimations.h b/client/battle/CBattleAnimations.h index 18104d558..c15520196 100644 --- a/client/battle/CBattleAnimations.h +++ b/client/battle/CBattleAnimations.h @@ -197,8 +197,16 @@ struct ProjectileInfo std::shared_ptr catapultInfo; // holds info about the parabolic trajectory of the cannon }; +class CRangedAttackAnimation : public CAttackAnimation +{ +public: + CRangedAttackAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender); +protected: + +}; + /// Shooting attack -class CShootingAnimation : public CAttackAnimation +class CShootingAnimation : public CRangedAttackAnimation { private: int catapultDamage; @@ -213,6 +221,18 @@ public: virtual ~CShootingAnimation(){}; }; +class CCastAnimation : public CRangedAttackAnimation +{ +public: + CCastAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender); + + bool init() override; + void nextFrame() override; + void endAnim() override; + +}; + + /// This class manages effect animation class CEffectAnimation : public CBattleAnimation { diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 7248f457a..b3cfcd2e9 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -1266,7 +1266,7 @@ void CBattleInterface::displayBattleFinished() curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897 } -void CBattleInterface::spellCast(const BattleSpellCast *sc) +void CBattleInterface::spellCast(const BattleSpellCast * sc) { const SpellID spellID(sc->id); const CSpell & spell = *spellID.toSpell(); @@ -1276,24 +1276,32 @@ void CBattleInterface::spellCast(const BattleSpellCast *sc) if (!castSoundPath.empty()) CCS->soundh->playSound(castSoundPath); + const auto casterStackID = sc->casterStack; + const CStack * casterStack = nullptr; + if(casterStackID >= 0) + { + casterStack = curInt->cb->battleGetStackByID(casterStackID); + } + Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos; //hero position by default { - const auto casterStackID = sc->casterStack; - - if (casterStackID > 0) + if(casterStack != nullptr) { - const CStack *casterStack = curInt->cb->battleGetStackByID(casterStackID); - if (casterStack != nullptr) - { - srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this); - srccoord.x += 250; - srccoord.y += 240; - } + srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this); + srccoord.x += 250; + srccoord.y += 240; } } - //todo: play custom cast animation - displaySpellCast(spellID, BattleHex::INVALID); + if(casterStack != nullptr && sc->activeCast) + { + //todo: custom cast animation for hero + displaySpellCast(spellID, casterStack->position); + + addNewAnim(new CCastAnimation(this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile))); + } + + waitForAnims(); //wait for cast animation //playing projectile animation if (sc->tile.isValid()) @@ -1328,7 +1336,8 @@ void CBattleInterface::spellCast(const BattleSpellCast *sc) addNewAnim(new CEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip)); } } - waitForAnims(); + + waitForAnims(); //wait for projectile animation displaySpellHit(spellID, sc->tile); diff --git a/client/battle/CBattleInterface.h b/client/battle/CBattleInterface.h index 5c0913208..a96b517e1 100644 --- a/client/battle/CBattleInterface.h +++ b/client/battle/CBattleInterface.h @@ -388,5 +388,6 @@ public: friend class CAttackAnimation; friend class CMeleeAttackAnimation; friend class CShootingAnimation; + friend class CCastAnimation; friend class CClickableHex; }; diff --git a/client/battle/CCreatureAnimation.cpp b/client/battle/CCreatureAnimation.cpp index 835e733a1..3fc1ac664 100644 --- a/client/battle/CCreatureAnimation.cpp +++ b/client/battle/CCreatureAnimation.cpp @@ -72,6 +72,9 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c case CCreatureAnim::CAST_UP: case CCreatureAnim::CAST_FRONT: case CCreatureAnim::CAST_DOWN: + case CCreatureAnim::VCMI_CAST_DOWN: + case CCreatureAnim::VCMI_CAST_FRONT: + case CCreatureAnim::VCMI_CAST_UP: return speed * 4 * creature->animation.attackAnimationTime / anim->framesInGroup(type); // as strange as it looks like "attackAnimationTime" does not affects melee attacks @@ -82,6 +85,10 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c case CCreatureAnim::HITTED: case CCreatureAnim::DEFENCE: case CCreatureAnim::DEATH: + case CCreatureAnim::DEATH_RANGED: + case CCreatureAnim::VCMI_2HEX_DOWN: + case CCreatureAnim::VCMI_2HEX_FRONT: + case CCreatureAnim::VCMI_2HEX_UP: return speed * 3 / anim->framesInGroup(type); case CCreatureAnim::TURN_L: @@ -93,11 +100,11 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c return speed / 3; case CCreatureAnim::DEAD: + case CCreatureAnim::DEAD_RANGED: return speed; default: - assert(0); - return 1; + return speed; } } @@ -128,9 +135,6 @@ CCreatureAnim::EAnimType CCreatureAnimation::getType() const void CCreatureAnimation::setType(CCreatureAnim::EAnimType type) { - assert(type >= 0); - assert(framesInGroup(type) != 0); - this->type = type; currentFrame = 0; once = false; @@ -162,6 +166,12 @@ CCreatureAnimation::CCreatureAnimation(const std::string & name_, TSpeedControll reverse->duplicateImage(CCreatureAnim::DEATH, reverse->size(CCreatureAnim::DEATH)-1, CCreatureAnim::DEAD); } + if(forward->size(CCreatureAnim::DEAD_RANGED) == 0 && forward->size(CCreatureAnim::DEATH_RANGED) != 0) + { + forward->duplicateImage(CCreatureAnim::DEATH_RANGED, forward->size(CCreatureAnim::DEATH_RANGED)-1, CCreatureAnim::DEAD_RANGED); + reverse->duplicateImage(CCreatureAnim::DEATH_RANGED, reverse->size(CCreatureAnim::DEATH_RANGED)-1, CCreatureAnim::DEAD_RANGED); + } + //TODO: get dimensions form CAnimation IImage * first = forward->getImage(0, type, true); @@ -191,6 +201,7 @@ bool CCreatureAnimation::incrementFrame(float timePassed) { elapsedTime += timePassed; currentFrame += timePassed * speed; + if (currentFrame >= float(framesInGroup(type))) { // just in case of extremely low fps (or insanely high speed) @@ -271,7 +282,7 @@ void CCreatureAnimation::genBorderPalette(IImage::BorderPallete & target) target[2] = addColors(genShadow(64), genBorderColor(getBorderStrength(elapsedTime), border)); } -void CCreatureAnimation::nextFrame(SDL_Surface *dest, bool attacker) +void CCreatureAnimation::nextFrame(SDL_Surface * dest, bool attacker) { size_t frame = floor(currentFrame); @@ -282,12 +293,15 @@ void CCreatureAnimation::nextFrame(SDL_Surface *dest, bool attacker) else image = reverse->getImage(frame, type); - IImage::BorderPallete borderPallete; - genBorderPalette(borderPallete); + if(image) + { + IImage::BorderPallete borderPallete; + genBorderPalette(borderPallete); - image->setBorderPallete(borderPallete); + image->setBorderPallete(borderPallete); - image->draw(dest, pos.x, pos.y); + image->draw(dest, pos.x, pos.y); + } } int CCreatureAnimation::framesInGroup(CCreatureAnim::EAnimType group) const @@ -298,7 +312,9 @@ int CCreatureAnimation::framesInGroup(CCreatureAnim::EAnimType group) const bool CCreatureAnimation::isDead() const { return getType() == CCreatureAnim::DEAD - || getType() == CCreatureAnim::DEATH; + || getType() == CCreatureAnim::DEATH + || getType() == CCreatureAnim::DEAD_RANGED + || getType() == CCreatureAnim::DEATH_RANGED; } bool CCreatureAnimation::isIdle() const diff --git a/client/battle/CCreatureAnimation.h b/client/battle/CCreatureAnimation.h index 1065930ed..86edc7b84 100644 --- a/client/battle/CCreatureAnimation.h +++ b/client/battle/CCreatureAnimation.h @@ -108,7 +108,7 @@ public: void playOnce(CCreatureAnim::EAnimType type); //plays once given stage of animation, then resets to 2 - int framesInGroup(CCreatureAnim::EAnimType group) const; //retirns number of fromes in given group + int framesInGroup(CCreatureAnim::EAnimType group) const; void pause(); void play(); diff --git a/client/gui/CAnimation.cpp b/client/gui/CAnimation.cpp index 66c81ad99..a7542418f 100644 --- a/client/gui/CAnimation.cpp +++ b/client/gui/CAnimation.cpp @@ -82,6 +82,8 @@ public: SDLImage(CDefFile *data, size_t frame, size_t group=0, bool compressed=false); //Load from bitmap file SDLImage(std::string filename, bool compressed=false); + + SDLImage(const JsonNode & conf); //Create using existing surface, extraRef will increase refcount on SDL_Surface SDLImage(SDL_Surface * from, bool extraRef); ~SDLImage(); @@ -803,15 +805,19 @@ void IImage::increaseRef() refCount++; } -SDLImage::SDLImage(CDefFile *data, size_t frame, size_t group, bool compressed): - surf(nullptr) +SDLImage::SDLImage(CDefFile * data, size_t frame, size_t group, bool compressed) + : surf(nullptr), + margins(0, 0), + fullSize(0, 0) { SDLImageLoader loader(this); data->loadFrame(frame, group, loader); } -SDLImage::SDLImage(SDL_Surface * from, bool extraRef): - margins(0,0) +SDLImage::SDLImage(SDL_Surface * from, bool extraRef) + : surf(nullptr), + margins(0, 0), + fullSize(0, 0) { surf = from; if (extraRef) @@ -820,8 +826,42 @@ SDLImage::SDLImage(SDL_Surface * from, bool extraRef): fullSize.y = surf->h; } -SDLImage::SDLImage(std::string filename, bool compressed): - margins(0,0) +SDLImage::SDLImage(const JsonNode & conf) + : surf(nullptr), + margins(0, 0), + fullSize(0, 0) +{ + std::string filename = conf["file"].String(); + + surf = BitmapHandler::loadBitmap(filename); + + if(surf == nullptr) + return; + + const JsonNode & jsonMargins = conf["margins"]; + + margins.x = jsonMargins["left"].Integer(); + margins.y = jsonMargins["top"].Integer(); + + fullSize.x = conf["width"].Integer(); + fullSize.y = conf["height"].Integer(); + + if(fullSize.x == 0) + { + fullSize.x = margins.x + surf->w + jsonMargins["right"].Integer(); + } + + if(fullSize.y == 0) + { + fullSize.y = margins.y + surf->h + jsonMargins["bottom"].Integer(); + } +} + + +SDLImage::SDLImage(std::string filename, bool compressed) + : surf(nullptr), + margins(0, 0), + fullSize(0, 0) { surf = BitmapHandler::loadBitmap(filename); @@ -851,9 +891,10 @@ SDLImage::SDLImage(std::string filename, bool compressed): } } + void SDLImage::draw(SDL_Surface *where, int posX, int posY, Rect *src, ui8 alpha) const { - if (!surf) + if(!surf) return; Rect destRect(posX, posY, surf->w, surf->h); @@ -996,7 +1037,6 @@ void SDLImage::setBorderPallete(const IImage::BorderPallete & borderPallete) } } - SDLImage::~SDLImage() { SDL_FreeSurface(surf); @@ -1300,34 +1340,34 @@ IImage * CAnimation::getFromExtraDef(std::string filename) return ret; } -bool CAnimation::loadFrame(CDefFile * file, size_t frame, size_t group) +bool CAnimation::loadFrame(size_t frame, size_t group) { - if (size(group) <= frame) + if(size(group) <= frame) { printError(frame, group, "LoadFrame"); return false; } - IImage *image = getImage(frame, group, false); - if (image) + IImage * image = getImage(frame, group, false); + if(image) { image->increaseRef(); return true; } //try to get image from def - if (source[group][frame].getType() == JsonNode::DATA_NULL) + if(source[group][frame].getType() == JsonNode::DATA_NULL) { - if (file) + if(defFile) { - auto frameList = file->getEntries(); + auto frameList = defFile->getEntries(); - if (vstd::contains(frameList, group) && frameList.at(group) > frame) // frame is present + if(vstd::contains(frameList, group) && frameList.at(group) > frame) // frame is present { - if (compressed) - images[group][frame] = new CompImage(file, frame, group); + if(compressed) + images[group][frame] = new CompImage(defFile, frame, group); else - images[group][frame] = new SDLImage(file, frame, group); + images[group][frame] = new SDLImage(defFile, frame, group); return true; } } @@ -1338,11 +1378,9 @@ bool CAnimation::loadFrame(CDefFile * file, size_t frame, size_t group) } else //load from separate file { - std::string filename = source[group][frame]["file"].String(); - - IImage * img = getFromExtraDef(filename); - if (!img) - img = new SDLImage(filename, compressed); + IImage * img = getFromExtraDef(source[group][frame]["file"].String()); + if(!img) + img = new SDLImage(source[group][frame]); images[group][frame] = img; return true; @@ -1373,30 +1411,37 @@ void CAnimation::initFromJson(const JsonNode & config) std::string basepath; basepath = config["basepath"].String(); - for(const JsonNode &group : config["sequences"].Vector()) + JsonNode base(JsonNode::DATA_STRUCT); + base["margins"] = config["margins"]; + base["width"] = config["width"]; + base["height"] = config["height"]; + + for(const JsonNode & group : config["sequences"].Vector()) { - size_t groupID = group["group"].Float();//TODO: string-to-value conversion("moving" -> MOVING) + size_t groupID = group["group"].Integer();//TODO: string-to-value conversion("moving" -> MOVING) source[groupID].clear(); - for(const JsonNode &frame : group["frames"].Vector()) + for(const JsonNode & frame : group["frames"].Vector()) { - source[groupID].push_back(JsonNode()); - std::string filename = frame.String(); - source[groupID].back()["file"].String() = basepath + filename; + JsonNode toAdd(JsonNode::DATA_STRUCT); + JsonUtils::inherit(toAdd, base); + toAdd["file"].String() = basepath + frame["file"].String(); + source[groupID].push_back(toAdd); } } - for(const JsonNode &node : config["images"].Vector()) + for(const JsonNode & node : config["images"].Vector()) { - size_t group = node["group"].Float(); - size_t frame = node["frame"].Float(); + size_t group = node["group"].Integer(); + size_t frame = node["frame"].Integer(); if (source[group].size() <= frame) source[group].resize(frame+1); - source[group][frame] = node; - std::string filename = node["file"].String(); - source[group][frame]["file"].String() = basepath + filename; + JsonNode toAdd(JsonNode::DATA_STRUCT); + JsonUtils::inherit(toAdd, base); + toAdd["file"].String() = basepath + node["file"].String(); + source[group][frame] = toAdd; } } @@ -1433,11 +1478,11 @@ void CAnimation::exportBitmaps(const boost::filesystem::path& path) const logGlobal->info("Exported %d frames to %s", counter, actualPath.string()); } -void CAnimation::init(CDefFile * file) +void CAnimation::init() { - if (file) + if(defFile) { - const std::map defEntries = file->getEntries(); + const std::map defEntries = defFile->getEntries(); for (auto & defEntry : defEntries) source[defEntry.first].resize(defEntry.second); @@ -1462,15 +1507,6 @@ void CAnimation::init(CDefFile * file) } } -CDefFile * CAnimation::getFile() const -{ - ResourceID identifier(std::string("SPRITES/") + name, EResType::ANIMATION); - - if (CResourceHandler::get()->existsResource(identifier)) - return new CDefFile(name); - return nullptr; -} - void CAnimation::printError(size_t frame, size_t group, std::string type) const { logGlobal->error("%s error: Request for frame not present in CAnimation! File name: %s, Group: %d, Frame: %d", type, name, group, frame); @@ -1479,15 +1515,20 @@ void CAnimation::printError(size_t frame, size_t group, std::string type) const CAnimation::CAnimation(std::string Name, bool Compressed): name(Name), compressed(Compressed), - preloaded(false) + preloaded(false), + defFile(nullptr) { size_t dotPos = name.find_last_of('.'); if ( dotPos!=-1 ) name.erase(dotPos); std::transform(name.begin(), name.end(), name.begin(), toupper); - CDefFile * file = getFile(); - init(file); - delete file; + + ResourceID resource(std::string("SPRITES/") + name, EResType::ANIMATION); + + if(CResourceHandler::get()->existsResource(resource)) + defFile = new CDefFile(name); + + init(); if(source.empty()) logAnim->error("Animation %s failed to load", Name); @@ -1496,9 +1537,10 @@ CAnimation::CAnimation(std::string Name, bool Compressed): CAnimation::CAnimation(): name(""), compressed(false), - preloaded(false) + preloaded(false), + defFile(nullptr) { - init(nullptr); + init(); } CAnimation::~CAnimation() @@ -1558,13 +1600,9 @@ IImage * CAnimation::getImage(size_t frame, size_t group, bool verbose) const void CAnimation::load() { - CDefFile * file = getFile(); - for (auto & elem : source) for (size_t image=0; image < elem.second.size(); image++) - loadFrame(file, image, elem.first); - - delete file; + loadFrame(image, elem.first); } void CAnimation::unload() @@ -1586,13 +1624,9 @@ void CAnimation::preload() void CAnimation::loadGroup(size_t group) { - CDefFile * file = getFile(); - if (vstd::contains(source, group)) for (size_t image=0; image < source[group].size(); image++) - loadFrame(file, image, group); - - delete file; + loadFrame(image, group); } void CAnimation::unloadGroup(size_t group) @@ -1604,9 +1638,7 @@ void CAnimation::unloadGroup(size_t group) void CAnimation::load(size_t frame, size_t group) { - CDefFile * file = getFile(); - loadFrame(file, frame, group); - delete file; + loadFrame(frame, group); } void CAnimation::unload(size_t frame, size_t group) diff --git a/client/gui/CAnimation.h b/client/gui/CAnimation.h index bf9e04ceb..1b4e3ab0d 100644 --- a/client/gui/CAnimation.h +++ b/client/gui/CAnimation.h @@ -78,18 +78,17 @@ private: bool preloaded; + CDefFile * defFile; + //loader, will be called by load(), require opened def file for loading from it. Returns true if image is loaded - bool loadFrame(CDefFile * file, size_t frame, size_t group); + bool loadFrame(size_t frame, size_t group); //unloadFrame, returns true if image has been unloaded ( either deleted or decreased refCount) bool unloadFrame(size_t frame, size_t group); //initialize animation from file void initFromJson(const JsonNode & input); - void init(CDefFile * file); - - //try to open def file - CDefFile * getFile() const; + void init(); //to get rid of copy-pasting error message :] void printError(size_t frame, size_t group, std::string type) const; @@ -99,7 +98,6 @@ private: IImage * getFromExtraDef(std::string filename); public: - CAnimation(std::string Name, bool Compressed = false); CAnimation(); ~CAnimation(); diff --git a/client/gui/SDL_Extensions.h b/client/gui/SDL_Extensions.h index 623b68df5..36b0380e5 100644 --- a/client/gui/SDL_Extensions.h +++ b/client/gui/SDL_Extensions.h @@ -218,8 +218,10 @@ namespace CSDL_Ext void stopTextInput(); void setColorKey(SDL_Surface * surface, SDL_Color color); + ///set key-color to 0,255,255 void setDefaultColorKey(SDL_Surface * surface); + ///set key-color to 0,255,255 only if it exactly mapped void setDefaultColorKeyPresize(SDL_Surface * surface); } diff --git a/client/widgets/Images.h b/client/widgets/Images.h index dda13fdc6..1cc38a639 100644 --- a/client/widgets/Images.h +++ b/client/widgets/Images.h @@ -181,7 +181,7 @@ public: HITTED=3, DEFENCE=4, DEATH=5, - //DEATH2=6, //unused? + DEATH_RANGED=6, TURN_L=7, TURN_R=8, //same //TURN_L2=9, //identical to previous? @@ -197,8 +197,16 @@ public: CAST_DOWN=19, MOVE_START=20, MOVE_END=21, - DEAD = 22 // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here + 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 + + VCMI_CAST_UP = 30, + VCMI_CAST_FRONT = 31, + VCMI_CAST_DOWN = 32, + VCMI_2HEX_UP = 40, + VCMI_2HEX_FRONT = 41, + VCMI_2HEX_DOWN = 42 }; private: diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 41175fed5..726105596 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1636,10 +1636,12 @@ struct BattleSpellCast : public CPackForClient manaGained = 0; casterStack = -1; castByHero = true; + activeCast = true; }; DLL_LINKAGE void applyGs(CGameState *gs); void applyCl(CClient *cl); + bool activeCast; ui8 side; //which hero did cast spell: 0 - attacker, 1 - defender ui32 id; //id of spell ui8 skill; //caster's skill level @@ -1663,6 +1665,7 @@ struct BattleSpellCast : public CPackForClient h & casterStack; h & castByHero; h & battleLog; + h & activeCast; } }; diff --git a/lib/spells/CDefaultSpellMechanics.cpp b/lib/spells/CDefaultSpellMechanics.cpp index 9ae701ffb..8a37774f6 100644 --- a/lib/spells/CDefaultSpellMechanics.cpp +++ b/lib/spells/CDefaultSpellMechanics.cpp @@ -199,6 +199,10 @@ void SpellCastContext::beforeCast() sc.manaGained = (manaChannel * spellCost) / 100; } } + + sc.activeCast = parameters.mode == ECastingMode::HERO_CASTING || + parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING || + parameters.mode == ECastingMode::ENCHANTER_CASTING; } void SpellCastContext::afterCast()