diff --git a/client/battle/CBattleAnimations.cpp b/client/battle/CBattleAnimations.cpp index 3845ba43d..6a161774d 100644 --- a/client/battle/CBattleAnimations.cpp +++ b/client/battle/CBattleAnimations.cpp @@ -20,6 +20,7 @@ #include "../../lib/BattleState.h" #include "../../lib/CTownHandler.h" #include "../../lib/mapObjects/CGTownInstance.h" +#include "../../lib/CSpellHandler.h" /* * CBattleAnimations.cpp, part of VCMI engine @@ -865,83 +866,81 @@ void CShootingAnimation::endAnim() delete this; } -CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx, int _dy, bool _Vflip, bool _areaEffect) -:CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), x(0), y(0), dx(_dx), dy(_dy), Vflip(_Vflip) , areaEffect(_areaEffect) +CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx, int _dy, bool _Vflip, bool _alignToBottom) + :CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), x(-1), y(-1), dx(_dx), dy(_dy), Vflip(_Vflip), alignToBottom(_alignToBottom) { logAnim->debugStream() << "Created spell anim for effect #" << effect; } -CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _areaEffect) -:CBattleAnimation(_owner), effect(-1), destTile(0), customAnim(_customAnim), x(_x), y(_y), dx(_dx), dy(_dy), Vflip(_Vflip), areaEffect(_areaEffect) +CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom) + :CBattleAnimation(_owner), effect(-1), destTile(BattleHex::INVALID), customAnim(_customAnim), x(_x), y(_y), dx(_dx), dy(_dy), Vflip(_Vflip), alignToBottom(_alignToBottom) { logAnim->debugStream() << "Created spell anim for " << customAnim; } +CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip, bool _alignToBottom) + :CBattleAnimation(_owner), effect(-1), destTile(_destTile), customAnim(_customAnim), x(-1), y(-1), dx(0), dy(0), Vflip(_Vflip), alignToBottom(_alignToBottom) +{ + logAnim->debugStream() << "Created spell anim for " << customAnim; +} + + bool CSpellEffectAnimation::init() { if(!isEarliest(true)) return false; - - if(effect == 12) //armageddon + + if(customAnim.empty() && effect != ui32(-1) && !graphics->battleACToDef[effect].empty()) + { + customAnim = graphics->battleACToDef[effect][0]; + } + + if(customAnim.empty()) { - if(effect == -1 || graphics->battleACToDef[effect].size() != 0) + endAnim(); + return false; + } + + const bool areaEffect = (!destTile.isValid() && x == -1 && y == -1); + + if(areaEffect) //f.e. armageddon + { + CDefHandler * anim = CDefHandler::giveDef(customAnim); + + for(int i=0; i * anim->width < owner->pos.w ; ++i) { - CDefHandler * anim; - if(customAnim.size()) - anim = CDefHandler::giveDef(customAnim); - else - anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]); - - if (Vflip) + for(int j=0; j * anim->height < owner->pos.h ; ++j) { - for (auto & elem : anim->ourImages) + BattleEffect be; + be.effectID = ID; + be.anim = CDefHandler::giveDef(customAnim); + if (Vflip) { - CSDL_Ext::VflipSurf(elem.bitmap); - } - } - - for(int i=0; i * anim->width < owner->pos.w ; ++i) - { - for(int j=0; j * anim->height < owner->pos.h ; ++j) - { - BattleEffect be; - be.effectID = ID; - be.anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]); - if (Vflip) + for (auto & elem : be.anim->ourImages) { - for (auto & elem : be.anim->ourImages) - { - CSDL_Ext::VflipSurf(elem.bitmap); - } + CSDL_Ext::VflipSurf(elem.bitmap); } - be.currentFrame = 0; - be.maxFrame = be.anim->ourImages.size(); - be.x = i * anim->width + owner->pos.x; - be.y = j * anim->height + owner->pos.y; - be.position = BattleHex::INVALID; - - owner->battleEffects.push_back(be); } + be.currentFrame = 0; + be.maxFrame = be.anim->ourImages.size(); + be.x = i * anim->width + owner->pos.x; + be.y = j * anim->height + owner->pos.y; + be.position = BattleHex::INVALID; + + owner->battleEffects.push_back(be); } } - else //there is nothing to play - { - endAnim(); - return false; - } + + delete anim; } else // Effects targeted at a specific creature/hex. { - if(effect == -1 || graphics->battleACToDef[effect].size() != 0) - { + const CStack* destStack = owner->getCurrentPlayerInterface()->cb->battleGetStackByPos(destTile, false); Rect &tilePos = owner->bfield[destTile]->pos; BattleEffect be; be.effectID = ID; - if(customAnim.size()) - be.anim = CDefHandler::giveDef(customAnim); - else - be.anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]); + be.anim = CDefHandler::giveDef(customAnim); if (Vflip) { @@ -953,28 +952,31 @@ bool CSpellEffectAnimation::init() be.currentFrame = 0; be.maxFrame = be.anim->ourImages.size(); - if(effect == 1) - be.maxFrame = 3; + + //todo: lightning anim frame count override + +// if(effect == 1) +// be.maxFrame = 3; - switch (effect) + if(x == -1) + { + be.x = tilePos.x + tilePos.w/2 - be.anim->width/2; + } + else { - case ui32(-1): be.x = x; + } + + if(y == -1) + { + if(alignToBottom) + be.y = tilePos.y + tilePos.h - be.anim->height; + else + be.y = tilePos.y - be.anim->height/2; + } + else + { be.y = y; - break; - case 0: // Prayer and Lightning Bolt. - case 1: - case 19: // Slow - // Position effect with it's bottom center touching the bottom center of affected tile(s). - be.x = tilePos.x + tilePos.w/2 - be.anim->width/2; - be.y = tilePos.y + tilePos.h - be.anim->height; - break; - - default: - // Position effect with it's center touching the top center of affected tile(s). - be.x = tilePos.x + tilePos.w/2 - be.anim->width/2; - be.y = tilePos.y - be.anim->height/2; - break; } // Correction for 2-hex creatures. @@ -988,12 +990,7 @@ bool CSpellEffectAnimation::init() be.position = destTile; owner->battleEffects.push_back(be); - } - else //there is nothing to play - { - endAnim(); - return false; - } + } //battleEffects return true; diff --git a/client/battle/CBattleAnimations.h b/client/battle/CBattleAnimations.h index 5ca8d6b50..5cbed2a7e 100644 --- a/client/battle/CBattleAnimations.h +++ b/client/battle/CBattleAnimations.h @@ -223,13 +223,14 @@ private: std::string customAnim; int x, y, dx, dy; bool Vflip; - bool areaEffect; + bool alignToBottom; public: bool init(); void nextFrame(); void endAnim(); - CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _areaEffect = true); - CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _areaEffect = true); + CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false); + CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false); + CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false); virtual ~CSpellEffectAnimation(){}; }; diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 02800292e..60504f7c6 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -1228,8 +1228,6 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc ) { const CSpell &spell = *CGI->spellh->objects[sc->id]; - std::vector< std::string > anims; //for magic arrow and ice bolt - const std::string& castSoundPath = spell.getCastSound(); if(!castSoundPath.empty()) @@ -1243,6 +1241,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc ) //playing projectile animation if(sc->tile.isValid()) { + //todo: srccoord of creature caster Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos; Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by projectile @@ -1275,58 +1274,31 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc ) waitForAnims(); //queuing hit animation - if(sc->tile.isValid()) - { - for(const CSpell::TAnimation & animation : spell.animationInfo.hit) - { - //Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by projectile - - //addNewAnim(new CSpellEffectAnimation(this, animation, destTile, 0, 0, false, areaEffect)); - } - } - else - { - //whole battlefield + for(const CSpell::TAnimation & animation : spell.animationInfo.hit) + { + addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, sc->tile, false, animation.verticalPosition == VerticalPosition::BOTTOM)); } - //queuing affect animation + //queuing affect /resist animation for (auto & elem : sc->affectedCres) { - for(const CSpell::TAnimation & animation : spell.animationInfo.affect) + BattleHex position = curInt->cb->battleGetStackByID(elem, false)->position; + + if(vstd::contains(sc->resisted,elem)) { - //Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by projectile - - //addNewAnim(new CSpellEffectAnimation(this, animation, destTile, 0, 0, false, areaEffect)); + displayEffect(78, position); + } + else + { + for(const CSpell::TAnimation & animation : spell.animationInfo.affect) + { + addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, position, false, animation.verticalPosition == VerticalPosition::BOTTOM)); + } } - //displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(elem, false)->position); } - - switch(sc->id) { - - case SpellID::LIGHTNING_BOLT: - case SpellID::TITANS_LIGHTNING_BOLT: - case SpellID::THUNDERBOLT: - case SpellID::CHAIN_LIGHTNING: //TODO: zigzag effect - for (auto & elem : sc->affectedCres) //in case we have multiple targets - { - displayEffect(1, curInt->cb->battleGetStackByID(elem, false)->position); - displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(elem, false)->position); - } - break; - case SpellID::DISPEL: - case SpellID::CURE: - case SpellID::RESURRECTION: - case SpellID::ANIMATE_DEAD: - case SpellID::DISPEL_HELPFUL_SPELLS: - case SpellID::SACRIFICE: //TODO: animation upon killed stack - for(auto & elem : sc->affectedCres) - { - displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(elem, false)->position); - } - break; case SpellID::SUMMON_FIRE_ELEMENTAL: case SpellID::SUMMON_EARTH_ELEMENTAL: case SpellID::SUMMON_WATER_ELEMENTAL: @@ -1337,18 +1309,12 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc ) break; } //switch(sc->id) - if (spell.isDamageSpell() && sc->affectedCres.empty()) //for example Inferno that causes no BattleStackAttacked - { - if(sc->tile.isValid() && graphics->battleACToDef.count(spell.mainEffectAnim)) //eg. when casting Lind Mine or Fire Wall - displayEffect (spell.mainEffectAnim, sc->tile); - } +// if (spell.isDamageSpell() && sc->affectedCres.empty()) //for example Inferno that causes no BattleStackAttacked +// { +// if(sc->tile.isValid() && graphics->battleACToDef.count(spell.mainEffectAnim)) //eg. when casting Lind Mine or Fire Wall +// displayEffect (spell.mainEffectAnim, sc->tile); +// } - //support for resistance - for(auto & elem : sc->resisted) - { - int tile = curInt->cb->battleGetStackByID(elem)->position; - displayEffect(78, tile); - } //displaying message in console bool customSpell = false; @@ -1511,16 +1477,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc ) void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse) { - int effID = sse.effect.back().sid; - if(effID != -1) //can be -1 for defensive stance effect - { - for(auto & elem : sse.stacks) - { - bool areaEffect(CGI->spellh->objects[effID]->getTargetType() == CSpell::ETargetType::NO_TARGET); - displayEffect(CGI->spellh->objects[effID]->mainEffectAnim, curInt->cb->battleGetStackByID(elem)->position, areaEffect); - } - } - else if (sse.stacks.size() == 1 && sse.effect.size() == 2) + if (sse.effect.back().sid == -1 && sse.stacks.size() == 1 && sse.effect.size() == 2) { const Bonus & bns = sse.effect.front(); if (bns.source == Bonus::OTHER && bns.type == Bonus::PRIMARY_SKILL) diff --git a/config/schemas/spell.json b/config/schemas/spell.json index 3aeb0b7d5..5415de513 100644 --- a/config/schemas/spell.json +++ b/config/schemas/spell.json @@ -11,8 +11,21 @@ "animationQueue":{ "type": "array", "items":{ - "type": "string", - "format": "defFile" + "anyOf":[ + { + //assumed verticalPosition: top + "type": "string", + "format": "defFile" + }, + { + "type": "object", + "properties":{ + "verticalPosition": {"type":"string", "enum":["top","bottom"]}, + "defName": {"type":"string", "format": "defFile"} + }, + "additionalProperties" : false + } + ] } }, "animation":{ diff --git a/config/spells/offensive.json b/config/spells/offensive.json index f4877529c..d2d08081b 100644 --- a/config/spells/offensive.json +++ b/config/spells/offensive.json @@ -68,7 +68,7 @@ "targetType": "CREATURE", "anim" : 38, "animation":{ - "hit":["C03SPA0", "C11SPA1"] + "hit":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"] }, "sounds": { "cast": "LIGHTBLT" @@ -119,7 +119,7 @@ "targetType": "CREATURE", "anim" : 38, "animation":{ - "affect":["C03SPA0", "C11SPA1"] + "affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"] }, "sounds": { "cast": "CHAINLTE" @@ -323,7 +323,7 @@ "targetType" : "CREATURE", "anim" : 38, "animation":{ - "hit":["C03SPA0", "C11SPA1"] + "hit":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"] }, "sounds": { "cast": "LIGHTBLT" diff --git a/config/spells/timed.json b/config/spells/timed.json index ee3efcb27..ce6a6eaea 100644 --- a/config/spells/timed.json +++ b/config/spells/timed.json @@ -591,7 +591,7 @@ "targetType" : "CREATURE", "anim" : 0, "animation":{ - "affect":["C10SPW"] + "affect":[{"defName":"C10SPW", "verticalPosition":"bottom"}] }, "sounds": { "cast": "PRAYER" @@ -901,7 +901,7 @@ "targetType" : "CREATURE", "anim" : 19, "animation":{ - "affect":["C09SPE0"] + "affect":[{"defName":"C09SPE0", "verticalPosition":"bottom"}] }, "sounds": { "cast": "MUCKMIRE" diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index 0051f581a..62523c06b 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -570,7 +570,7 @@ std::string CSpell::AnimationInfo::selectProjectile(const double angle) const if(info.minimumAngle < angle && info.minimumAngle > maximum) { maximum = info.minimumAngle; - res = info.defName; + res = info.resourceName; } } @@ -886,16 +886,39 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json) spell->iconScroll = graphicsNode["iconScroll"].String(); const JsonNode & animationNode = json["animation"]; - spell->animationInfo.affect = animationNode["affect"].convertTo(); - spell->animationInfo.cast = animationNode["cast"].convertTo(); - spell->animationInfo.hit = animationNode["hit"].convertTo(); + + auto loadAnimationQueue = [&](const std::string & jsonName, CSpell::TAnimationQueue & q) + { + auto queueNode = animationNode[jsonName].Vector(); + for(const JsonNode & item : queueNode) + { + CSpell::TAnimation newItem; + newItem.verticalPosition = VerticalPosition::TOP; + + if(item.getType() == JsonNode::DATA_STRING) + newItem.resourceName = item.String(); + else if(item.getType() == JsonNode::DATA_STRUCT) + { + newItem.resourceName = item["defName"].String(); + + auto vPosStr = item["verticalPosition"].String(); + if("bottom" == vPosStr) + newItem.verticalPosition = VerticalPosition::BOTTOM; + } + q.push_back(newItem); + } + }; + + loadAnimationQueue("affect", spell->animationInfo.affect); + loadAnimationQueue("cast", spell->animationInfo.cast); + loadAnimationQueue("hit", spell->animationInfo.hit); const JsonVector & projectile = animationNode["projectile"].Vector(); for(const JsonNode & item : projectile) { CSpell::ProjectileInfo info; - info.defName = item["defName"].String(); + info.resourceName = item["defName"].String(); info.minimumAngle = item["minimumAngle"].Float(); spell->animationInfo.projectile.push_back(info); diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index 64f1b42e1..72fe5052d 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -73,6 +73,7 @@ public: const BattleInfo * cb; }; +enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM}; class DLL_LINKAGE CSpell { @@ -84,15 +85,26 @@ public: double minimumAngle; ///resource name - std::string defName; + std::string resourceName; template void serialize(Handler &h, const int version) { - h & minimumAngle & defName; + h & minimumAngle & resourceName; } }; - typedef std::string TAnimation; + struct AnimationItem + { + std::string resourceName; + VerticalPosition verticalPosition; + + template void serialize(Handler &h, const int version) + { + h & resourceName & verticalPosition; + } + }; + + typedef AnimationItem TAnimation; typedef std::vector TAnimationQueue; struct DLL_LINKAGE AnimationInfo