1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-25 21:38:59 +02:00

Implemented configurable hit/affect animation

* need more testing
This commit is contained in:
AlexVinS 2014-11-27 16:51:16 +03:00
parent f4cf12d3f8
commit 75b93b070d
8 changed files with 159 additions and 156 deletions

@ -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;

@ -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(){};
};

@ -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)

@ -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":{

@ -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"

@ -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"

@ -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<CSpell::TAnimationQueue>();
spell->animationInfo.cast = animationNode["cast"].convertTo<CSpell::TAnimationQueue>();
spell->animationInfo.hit = animationNode["hit"].convertTo<CSpell::TAnimationQueue>();
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);

@ -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 <typename Handler> 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 <typename Handler> void serialize(Handler &h, const int version)
{
h & resourceName & verticalPosition;
}
};
typedef AnimationItem TAnimation;
typedef std::vector<TAnimation> TAnimationQueue;
struct DLL_LINKAGE AnimationInfo