mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
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.
This commit is contained in:
parent
494872937a
commit
270e1b75ce
@ -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)))
|
||||
|
@ -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<CReverseAnimation *>(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),
|
||||
|
@ -197,8 +197,16 @@ struct ProjectileInfo
|
||||
std::shared_ptr<CatapultProjectileInfo> 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
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
|
@ -388,5 +388,6 @@ public:
|
||||
friend class CAttackAnimation;
|
||||
friend class CMeleeAttackAnimation;
|
||||
friend class CShootingAnimation;
|
||||
friend class CCastAnimation;
|
||||
friend class CClickableHex;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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<size_t, size_t> defEntries = file->getEntries();
|
||||
const std::map<size_t, size_t> 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)
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user