1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-26 22:57:00 +02:00

more improvements for battle animations

- synchronized attack/defence animation
- spell animation speed uses game settings
- added logging domain for battle animations

- fixed disrupting ray duration
This commit is contained in:
Ivan Savenko 2013-07-16 18:12:47 +00:00
parent 4f7c6b8d34
commit 1a77fee7f7
10 changed files with 137 additions and 65 deletions

View File

@ -866,7 +866,7 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
if (defender && !elem.isSecondary()) if (defender && !elem.isSecondary())
battleInt->displayEffect(elem.effect, defender->position); battleInt->displayEffect(elem.effect, defender->position);
} }
bool shooting = (LOCPLINT->curAction ? LOCPLINT->curAction->actionType == Battle::SHOOT : false); //FIXME: why action is deleted during enchanter cast? bool shooting = (LOCPLINT->curAction ? LOCPLINT->curAction->actionType != Battle::WALK_AND_ATTACK : false); //FIXME: why action is deleted during enchanter cast?
StackAttackedInfo to_put = {defender, elem.damageAmount, elem.killedAmount, attacker, shooting, elem.killed(), elem.willRebirth(), elem.cloneKilled()}; StackAttackedInfo to_put = {defender, elem.damageAmount, elem.killedAmount, attacker, shooting, elem.killed(), elem.willRebirth(), elem.cloneKilled()};
arg.push_back(to_put); arg.push_back(to_put);
} }

View File

@ -32,10 +32,18 @@
CBattleAnimation::CBattleAnimation(CBattleInterface * _owner) CBattleAnimation::CBattleAnimation(CBattleInterface * _owner)
: owner(_owner), ID(_owner->animIDhelper++) : owner(_owner), ID(_owner->animIDhelper++)
{} {
logAnim->traceStream() << "Animation #" << ID << " created";
}
CBattleAnimation::~CBattleAnimation()
{
logAnim->traceStream() << "Animation #" << ID << " deleted";
}
void CBattleAnimation::endAnim() void CBattleAnimation::endAnim()
{ {
logAnim->traceStream() << "Animation #" << ID << " ended, type is " << typeid(this).name();
for(auto & elem : owner->pendingAnims) for(auto & elem : owner->pendingAnims)
{ {
if(elem.first == this) if(elem.first == this)
@ -58,7 +66,7 @@ bool CBattleAnimation::isEarliest(bool perStackConcurrency)
if(perStackConcurrency && stAnim && thAnim && stAnim->stack->ID != thAnim->stack->ID) if(perStackConcurrency && stAnim && thAnim && stAnim->stack->ID != thAnim->stack->ID)
continue; continue;
if(sen && thSen && sen != thSen && perStackConcurrency) if(perStackConcurrency && sen && thSen && sen != thSen)
continue; continue;
CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(stAnim); CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(stAnim);
@ -105,6 +113,17 @@ void CAttackAnimation::endAnim()
bool CAttackAnimation::checkInitialConditions() bool CAttackAnimation::checkInitialConditions()
{ {
for(auto & elem : owner->pendingAnims)
{
CBattleStackAnimation * stAnim = dynamic_cast<CBattleStackAnimation *>(elem.first);
CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(stAnim);
if(revAnim) // enemy must be fully reversed
{
if (revAnim->stack->ID == attackedStack->ID)
return false;
}
}
return isEarliest(false); return isEarliest(false);
} }
@ -124,7 +143,7 @@ CAttackAnimation::CAttackAnimation(CBattleInterface *_owner, const CStack *attac
CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, CBattleInterface * _owner) CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, CBattleInterface * _owner)
: CBattleStackAnimation(_owner, _attackedInfo.defender), : CBattleStackAnimation(_owner, _attackedInfo.defender),
attacker(_attackedInfo.attacker), byShooting(_attackedInfo.byShooting), attacker(_attackedInfo.attacker), rangedAttack(_attackedInfo.rangedAttack),
killed(_attackedInfo.killed) killed(_attackedInfo.killed)
{} {}
@ -144,23 +163,15 @@ bool CDefenceAnimation::init()
if(attAnim && attAnim->stack->ID != stack->ID) if(attAnim && attAnim->stack->ID != stack->ID)
continue; continue;
if(attacker != nullptr)
{
int attackerAnimType = owner->creAnims[attacker->ID]->getType();
if( ( attackerAnimType == CCreatureAnim::ATTACK_UP ||
attackerAnimType == CCreatureAnim::ATTACK_FRONT ||
attackerAnimType == CCreatureAnim::ATTACK_DOWN ) )
return false;
}
CReverseAnimation * animAsRev = dynamic_cast<CReverseAnimation *>(elem.first); CReverseAnimation * animAsRev = dynamic_cast<CReverseAnimation *>(elem.first);
if(animAsRev && animAsRev->priority) if(animAsRev /*&& animAsRev->priority*/)
return false; return false;
if(elem.first) if(elem.first)
vstd::amin(lowestMoveID, elem.first->ID); vstd::amin(lowestMoveID, elem.first->ID);
} }
if(ID > lowestMoveID) if(ID > lowestMoveID)
return false; return false;
@ -172,7 +183,7 @@ bool CDefenceAnimation::init()
} }
//unit reversed //unit reversed
if(byShooting) //delay hit animation if(rangedAttack) //delay hit animation
{ {
for(std::list<ProjectileInfo>::const_iterator it = owner->projectiles.begin(); it != owner->projectiles.end(); ++it) for(std::list<ProjectileInfo>::const_iterator it = owner->projectiles.begin(); it != owner->projectiles.end(); ++it)
{ {
@ -183,27 +194,61 @@ bool CDefenceAnimation::init()
} }
} }
//initializing // synchronize animation with attacker, unless defending or attacked by shooter:
if(killed) // wait for 1/2 of attack animation
if (!rangedAttack && getMyAnimType() != CCreatureAnim::DEFENCE)
{ {
CCS->soundh->playSound(battle_sound(stack->getCreature(), killed)); float fps = AnimationControls::getCreatureAnimationSpeed(
myAnim->setType(CCreatureAnim::DEATH); //death stack->getCreature(), owner->creAnims[stack->ID], getMyAnimType());
timeToWait = myAnim->framesInGroup(getMyAnimType()) / fps;
myAnim->setType(CCreatureAnim::HOLDING);
} }
else else
{ {
// TODO: this block doesn't seems correct if the unit is defending. timeToWait = 0;
CCS->soundh->playSound(battle_sound(stack->getCreature(), wince)); startAnimation();
myAnim->setType(CCreatureAnim::HITTED); //getting hit
} }
myAnim->onAnimationReset += std::bind(&CDefenceAnimation::endAnim, this);
return true; //initialized successfuly return true; //initialized successfuly
} }
std::string CDefenceAnimation::getMySound()
{
if(killed)
return battle_sound(stack->getCreature(), killed);
if (stack->valOfBonuses(Selector::durationType(Bonus::STACK_GETS_TURN)))
return battle_sound(stack->getCreature(), defend);
return battle_sound(stack->getCreature(), wince);
}
CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType()
{
if(killed)
return CCreatureAnim::DEATH;
if (stack->valOfBonuses(Selector::durationType(Bonus::STACK_GETS_TURN)))
return CCreatureAnim::DEFENCE;
return CCreatureAnim::HITTED;
}
void CDefenceAnimation::startAnimation()
{
CCS->soundh->playSound(getMySound());
myAnim->setType(getMyAnimType());
myAnim->onAnimationReset += std::bind(&CDefenceAnimation::endAnim, this);
}
void CDefenceAnimation::nextFrame() void CDefenceAnimation::nextFrame()
{ {
assert(myAnim->getType() == CCreatureAnim::HITTED || myAnim->getType() == CCreatureAnim::DEATH); if (timeToWait > 0)
{
timeToWait -= float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000;
if (timeToWait <= 0)
startAnimation();
}
CBattleAnimation::nextFrame(); CBattleAnimation::nextFrame();
} }
@ -262,6 +307,12 @@ bool CMeleeAttackAnimation::init()
owner->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn, true)); owner->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn, true));
return false; return false;
} }
// opponent must face attacker ( = different directions) before he can be attacked
if (attackingStack && attackedStack &&
owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
return false;
//reversed //reversed
shooting = false; shooting = false;
@ -386,7 +437,7 @@ bool CMovementAnimation::init()
{ {
float distance = sqrt(distanceX * distanceX + distanceY * distanceY); float distance = sqrt(distanceX * distanceX + distanceY * distanceY);
timeToMove *= distance / AnimationControls::getFlightDistance(stack->getCreature()); timeToMove *= AnimationControls::getFlightDistance(stack->getCreature()) / distance;
} }
return true; return true;
@ -614,6 +665,11 @@ bool CShootingAnimation::init()
return false; return false;
} }
// opponent must face attacker ( = different directions) before he can be attacked
if (attackingStack && attackedStack &&
owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
return false;
// Create the projectile animation // Create the projectile animation
//maximal angle in radians between straight horizontal line and shooting line for which shot is considered to be straight (absoulte value) //maximal angle in radians between straight horizontal line and shooting line for which shot is considered to be straight (absoulte value)
@ -817,7 +873,7 @@ bool CSpellEffectAnimation::init()
CSDL_Ext::VflipSurf(elem.bitmap); CSDL_Ext::VflipSurf(elem.bitmap);
} }
} }
be.frame = 0; be.currentFrame = 0;
be.maxFrame = be.anim->ourImages.size(); be.maxFrame = be.anim->ourImages.size();
be.x = i * anim->width + owner->pos.x; be.x = i * anim->width + owner->pos.x;
be.y = j * anim->height + owner->pos.y; be.y = j * anim->height + owner->pos.y;
@ -854,7 +910,7 @@ bool CSpellEffectAnimation::init()
} }
} }
be.frame = 0; be.currentFrame = 0;
be.maxFrame = be.anim->ourImages.size(); be.maxFrame = be.anim->ourImages.size();
if(effect == 1) if(effect == 1)
be.maxFrame = 3; be.maxFrame = 3;
@ -909,9 +965,9 @@ void CSpellEffectAnimation::nextFrame()
{ {
if(elem.effectID == ID) if(elem.effectID == ID)
{ {
++(elem.frame); elem.currentFrame += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
if(elem.frame == elem.maxFrame) if(elem.currentFrame >= elem.maxFrame)
{ {
endAnim(); endAnim();
break; break;

View File

@ -28,7 +28,7 @@ public:
virtual bool init() = 0; //to be called - if returned false, call again until returns true virtual bool init() = 0; //to be called - if returned false, call again until returns true
virtual void nextFrame() {} //call every new frame virtual void nextFrame() {} //call every new frame
virtual void endAnim(); //to be called mostly internally; in this class it removes animation from pendingAnims list virtual void endAnim(); //to be called mostly internally; in this class it removes animation from pendingAnims list
virtual ~CBattleAnimation(){} virtual ~CBattleAnimation();
bool isEarliest(bool perStackConcurrency); //determines if this animation is earliest of all bool isEarliest(bool perStackConcurrency); //determines if this animation is earliest of all
@ -69,11 +69,16 @@ public:
/// Animation of a defending unit /// Animation of a defending unit
class CDefenceAnimation : public CBattleStackAnimation class CDefenceAnimation : public CBattleStackAnimation
{ {
private: CCreatureAnim::EAnimType getMyAnimType();
//std::vector<StackAttackedInfo> attackedInfos; std::string getMySound();
void startAnimation();
const CStack * attacker; //attacking stack const CStack * attacker; //attacking stack
bool byShooting; //if true, stack has been attacked by shooting bool rangedAttack; //if true, stack has been attacked by shooting
bool killed; //if true, stack has been killed bool killed; //if true, stack has been killed
float timeToWait; // for how long this animation should be paused
public: public:
bool init(); bool init();
void nextFrame(); void nextFrame();

View File

@ -1010,7 +1010,10 @@ void CBattleInterface::showBattleEffects(const std::vector<const BattleEffect *>
{ {
for(auto & elem : battleEffects) for(auto & elem : battleEffects)
{ {
SDL_Surface * bitmapToBlit = elem->anim->ourImages[(elem->frame)%elem->anim->ourImages.size()].bitmap; int currentFrame = floor(elem->currentFrame);
currentFrame %= elem->anim->ourImages.size();
SDL_Surface * bitmapToBlit = elem->anim->ourImages[currentFrame].bitmap;
SDL_Rect temp_rect = genRect(bitmapToBlit->h, bitmapToBlit->w, elem->x, elem->y); SDL_Rect temp_rect = genRect(bitmapToBlit->h, bitmapToBlit->w, elem->x, elem->y);
SDL_BlitSurface(bitmapToBlit, nullptr, to, &temp_rect); SDL_BlitSurface(bitmapToBlit, nullptr, to, &temp_rect);
} }
@ -1520,16 +1523,11 @@ void CBattleInterface::stackAttacking( const CStack * attacker, BattleHex dest,
{ {
addNewAnim(new CMeleeAttackAnimation(this, attacker, dest, attacked)); addNewAnim(new CMeleeAttackAnimation(this, attacker, dest, attacked));
} }
waitForAnims(); //waitForAnims();
} }
void CBattleInterface::newRoundFirst( int round ) void CBattleInterface::newRoundFirst( int round )
{ {
//handle regeneration
std::vector<const CStack*> stacks = curInt->cb->battleGetStacks(); //gets only alive stacks
// for(const CStack *s : stacks)
// {
// }
waitForAnims(); waitForAnims();
} }
@ -1725,12 +1723,13 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
//displaying animation //displaying animation
CDefEssential * animDef = CDefHandler::giveDefEss(animToDisplay); CDefEssential * animDef = CDefHandler::giveDefEss(animToDisplay);
double diffX = (destcoord.x - srccoord.x)*(destcoord.x - srccoord.x);
double diffY = (destcoord.y - srccoord.y)*(destcoord.y - srccoord.y);
double distance = sqrt(diffX + diffY);
int steps = sqrt(static_cast<double>((destcoord.x - srccoord.x)*(destcoord.x - srccoord.x) + (destcoord.y - srccoord.y) * (destcoord.y - srccoord.y))) / 40; int steps = distance / AnimationControls::getSpellEffectSpeed() + 1;
if(steps <= 0) int dx = (destcoord.x - srccoord.x - animDef->ourImages[0].bitmap->w)/steps;
steps = 1; int dy = (destcoord.y - srccoord.y - animDef->ourImages[0].bitmap->h)/steps;
int dx = (destcoord.x - srccoord.x - animDef->ourImages[0].bitmap->w)/steps, dy = (destcoord.y - srccoord.y - animDef->ourImages[0].bitmap->h)/steps;
delete animDef; delete animDef;
addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip)); addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
@ -2614,7 +2613,7 @@ void CBattleInterface::showQueue()
void CBattleInterface::startAction(const BattleAction* action) void CBattleInterface::startAction(const BattleAction* action)
{ {
setActiveStack(nullptr); //setActiveStack(nullptr);
setHoveredStack(nullptr); setHoveredStack(nullptr);
if(action->actionType == Battle::END_TACTIC_PHASE) if(action->actionType == Battle::END_TACTIC_PHASE)

View File

@ -57,7 +57,7 @@ struct StackAttackedInfo
unsigned int dmg; //damage dealt unsigned int dmg; //damage dealt
unsigned int amountKilled; //how many creatures in stack has been killed unsigned int amountKilled; //how many creatures in stack has been killed
const CStack * attacker; //attacking stack const CStack * attacker; //attacking stack
bool byShooting; //if true, stack has been attacked by shooting bool rangedAttack; //if true, stack has been attacked by shooting
bool killed; //if true, stack has been killed bool killed; //if true, stack has been killed
bool rebirth; //if true, play rebirth animation after all bool rebirth; //if true, play rebirth animation after all
bool cloneKilled; bool cloneKilled;
@ -67,7 +67,8 @@ struct StackAttackedInfo
struct BattleEffect struct BattleEffect
{ {
int x, y; //position on the screen int x, y; //position on the screen
int frame, maxFrame; float currentFrame;
int maxFrame;
CDefHandler * anim; //animation to display CDefHandler * anim; //animation to display
int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim
BattleHex position; //Indicates if effect which hex the effect is drawn on BattleHex position; //Indicates if effect which hex the effect is drawn on

View File

@ -48,11 +48,8 @@ CCreatureAnimation * AnimationControls::getAnimation(const CCreature * creature)
float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, const CCreatureAnimation * anim, size_t group) float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, const CCreatureAnimation * anim, size_t group)
{ {
// possible new fields for creature format // possible new fields for creature format:
//Shoot Animation Time //split "Attack time" into "Shoot Time" and "Cast Time"
//Cast Animation Time
//Defence and/or Death Animation Time
// a lot of arbitrary multipliers, mostly to make animation speed closer to H3 // a lot of arbitrary multipliers, mostly to make animation speed closer to H3
CCreatureAnim::EAnimType type = CCreatureAnim::EAnimType(group); CCreatureAnim::EAnimType type = CCreatureAnim::EAnimType(group);
@ -69,9 +66,6 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
case CCreatureAnim::HOLDING: case CCreatureAnim::HOLDING:
return baseSpeed; return baseSpeed;
case CCreatureAnim::ATTACK_UP:
case CCreatureAnim::ATTACK_FRONT:
case CCreatureAnim::ATTACK_DOWN:
case CCreatureAnim::SHOOT_UP: case CCreatureAnim::SHOOT_UP:
case CCreatureAnim::SHOOT_FRONT: case CCreatureAnim::SHOOT_FRONT:
case CCreatureAnim::SHOOT_DOWN: case CCreatureAnim::SHOOT_DOWN:
@ -80,6 +74,18 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
case CCreatureAnim::CAST_DOWN: case CCreatureAnim::CAST_DOWN:
return speed * 2 / creature->animation.attackAnimationTime / anim->framesInGroup(type); return speed * 2 / creature->animation.attackAnimationTime / anim->framesInGroup(type);
// as strange as it looks like "attackAnimationTime" does not affects melee attacks
// necessary because length of attack animation must be same for all creatures for synchronization
case CCreatureAnim::ATTACK_UP:
case CCreatureAnim::ATTACK_FRONT:
case CCreatureAnim::ATTACK_DOWN:
case CCreatureAnim::DEFENCE:
return speed * 2 / anim->framesInGroup(type);
case CCreatureAnim::DEATH:
case CCreatureAnim::HITTED: // time-wise equals 1/2 of attack animation length
return speed / anim->framesInGroup(type);
case CCreatureAnim::TURN_L: case CCreatureAnim::TURN_L:
case CCreatureAnim::TURN_R: case CCreatureAnim::TURN_R:
return speed; return speed;
@ -88,9 +94,6 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
case CCreatureAnim::MOVE_END: case CCreatureAnim::MOVE_END:
return speed / 5; return speed / 5;
case CCreatureAnim::HITTED:
case CCreatureAnim::DEFENCE:
case CCreatureAnim::DEATH:
case CCreatureAnim::DEAD: case CCreatureAnim::DEAD:
return speed / 5; return speed / 5;
@ -105,6 +108,11 @@ float AnimationControls::getProjectileSpeed()
return settings["battle"]["animationSpeed"].Float() * 100; return settings["battle"]["animationSpeed"].Float() * 100;
} }
float AnimationControls::getSpellEffectSpeed()
{
return settings["battle"]["animationSpeed"].Float() * 60;
}
float AnimationControls::getMovementDuration(const CCreature * creature) float AnimationControls::getMovementDuration(const CCreature * creature)
{ {
return settings["battle"]["animationSpeed"].Float() * 4 / creature->animation.walkAnimationTime; return settings["battle"]["animationSpeed"].Float() * 4 / creature->animation.walkAnimationTime;
@ -201,7 +209,7 @@ bool CCreatureAnimation::incrementFrame(float timePassed)
currentFrame += timePassed * speed; currentFrame += timePassed * speed;
if (currentFrame >= float(framesInGroup(type))) if (currentFrame >= float(framesInGroup(type)))
{ {
// just in case of extremely low fps // just in case of extremely low fps (or insanely high speed)
while (currentFrame >= float(framesInGroup(type))) while (currentFrame >= float(framesInGroup(type)))
currentFrame -= framesInGroup(type); currentFrame -= framesInGroup(type);

View File

@ -28,13 +28,16 @@ namespace AnimationControls
/// creates animation object with preset speed control /// creates animation object with preset speed control
CCreatureAnimation * getAnimation(const CCreature * creature); CCreatureAnimation * getAnimation(const CCreature * creature);
/// returns animation speed of specific group, taking in mind game setting /// returns animation speed of specific group, taking in mind game setting (in frames per second)
float getCreatureAnimationSpeed(const CCreature * creature, const CCreatureAnimation * anim, size_t groupID); float getCreatureAnimationSpeed(const CCreature * creature, const CCreatureAnimation * anim, size_t groupID);
/// returns how far projectile should move each frame /// returns how far projectile should move each frame
/// TODO: make it time-based /// TODO: make it time-based
float getProjectileSpeed(); float getProjectileSpeed();
/// returns speed of any spell effects, including any special effects like morale (in frames per second)
float getSpellEffectSpeed();
/// returns duration of full movement animation, in seconds. Needed to move animation on screen /// returns duration of full movement animation, in seconds. Needed to move animation on screen
float getMovementDuration(const CCreature * creature); float getMovementDuration(const CCreature * creature);

View File

@ -560,7 +560,7 @@
"type": "PRIMARY_SKILL", "type": "PRIMARY_SKILL",
"subtype": "primSkill.defence", "subtype": "primSkill.defence",
"valueType": "ADDITIVE_VALUE", "valueType": "ADDITIVE_VALUE",
"duration": "N_TURNS", "duration": "ONE_BATTLE",
"values":[-3,-3,-4,-5] "values":[-3,-3,-4,-5]
} }
] ]

View File

@ -54,10 +54,9 @@ boost::recursive_mutex CLogManager::smx;
DLL_LINKAGE CLogger * logGlobal = CLogger::getGlobalLogger(); DLL_LINKAGE CLogger * logGlobal = CLogger::getGlobalLogger();
DLL_LINKAGE CLogger * logBonus = CLogger::getLogger(CLoggerDomain("bonus")); DLL_LINKAGE CLogger * logBonus = CLogger::getLogger(CLoggerDomain("bonus"));
DLL_LINKAGE CLogger * logNetwork = CLogger::getLogger(CLoggerDomain("network")); DLL_LINKAGE CLogger * logNetwork = CLogger::getLogger(CLoggerDomain("network"));
DLL_LINKAGE CLogger * logAi = CLogger::getLogger(CLoggerDomain("ai")); DLL_LINKAGE CLogger * logAi = CLogger::getLogger(CLoggerDomain("ai"));
DLL_LINKAGE CLogger * logAnim = CLogger::getLogger(CLoggerDomain("animation"));
CLogger * CLogger::getLogger(const CLoggerDomain & domain) CLogger * CLogger::getLogger(const CLoggerDomain & domain)
{ {

View File

@ -125,6 +125,7 @@ extern DLL_LINKAGE CLogger * logGlobal;
extern DLL_LINKAGE CLogger * logBonus; extern DLL_LINKAGE CLogger * logBonus;
extern DLL_LINKAGE CLogger * logNetwork; extern DLL_LINKAGE CLogger * logNetwork;
extern DLL_LINKAGE CLogger * logAi; extern DLL_LINKAGE CLogger * logAi;
extern DLL_LINKAGE CLogger * logAnim;
/// RAII class for tracing the program execution. /// RAII class for tracing the program execution.
/// It prints "Leaving function XYZ" automatically when the object gets destructed. /// It prints "Leaving function XYZ" automatically when the object gets destructed.