void CReverseAnimation::setupSecondPart() { if(!stack) { endAnim(); return; } rotateStack(owner, stack, hex); if(myAnim->framesInGroup(CCreatureAnim::TURN_R)) { myAnim->setType(CCreatureAnim::TURN_R); myAnim->onAnimationReset += std::bind(&CReverseAnimation::endAnim, this); } else endAnim(); }
bool CMovementEndAnimation::init() { if( !isEarliest(true) ) return false; if(!stack || myAnim->framesInGroup(CCreatureAnim::MOVE_END) == 0 || myAnim->isDead()) { endAnim(); return false; } CCS->soundh->playSound(battle_sound(stack->getCreature(), endMoving)); myAnim->setType(CCreatureAnim::MOVE_END); myAnim->onAnimationReset += std::bind(&CMovementEndAnimation::endAnim, this); return true; }
void CSpellEffectAnimation::nextFrame() { //notice: there may be more than one effect in owner->battleEffects correcponding to this animation (ie. armageddon) for(auto & elem : owner->battleEffects) { if(elem.effectID == ID) { elem.currentFrame += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000; if(elem.currentFrame >= elem.maxFrame) { endAnim(); break; } else { elem.x += dx; elem.y += dy; } } } }
bool CReverseAnimation::init() { if(myAnim == nullptr || myAnim->isDead()) { endAnim(); return false; //there is no such creature } if(!priority && !isEarliest(false)) return false; if(myAnim->framesInGroup(CCreatureAnim::TURN_L)) { myAnim->setType(CCreatureAnim::TURN_L); myAnim->onAnimationReset += std::bind(&CReverseAnimation::setupSecondPart, this); } else { setupSecondPart(); } return true; }
void CMovementAnimation::nextFrame() { progress += float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000 * timeToMove; //moving instructions myAnim->pos.x = static_cast<Sint16>(begX + distanceX * progress ); myAnim->pos.y = static_cast<Sint16>(begY + distanceY * progress ); CBattleAnimation::nextFrame(); if(progress >= 1.0) { // Sets the position of the creature animation sprites Point coords = CClickableHex::getXYUnitAnim(nextHex, stack, owner); myAnim->pos = coords; // true if creature haven't reached the final destination hex if ((curentMoveIndex + 1) < destTiles.size()) { // update the next hex field which has to be reached by the stack curentMoveIndex++; oldPos = nextHex; nextHex = destTiles[curentMoveIndex]; // re-init animation for(auto & elem : owner->pendingAnims) { if (elem.first == this) { elem.second = false; break; } } } else endAnim(); } }
bool CSpellEffectAnimation::init() { if(!isEarliest(true)) return false; if(effect == 12) //armageddon { if(effect == -1 || graphics->battleACToDef[effect].size() != 0) { CDefHandler * anim; if(customAnim.size()) anim = CDefHandler::giveDef(customAnim); else anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]); if (Vflip) { for (auto & elem : anim->ourImages) { 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) { 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); } } } else //there is nothing to play { endAnim(); return false; } } 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]); if (Vflip) { for (auto & elem : be.anim->ourImages) { CSDL_Ext::VflipSurf(elem.bitmap); } } be.currentFrame = 0; be.maxFrame = be.anim->ourImages.size(); if(effect == 1) be.maxFrame = 3; switch (effect) { case ui32(-1): be.x = x; 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. if (destStack != nullptr && destStack->doubleWide()) be.x += (destStack->attackerOwned ? -1 : 1)*tilePos.w/2; //Indicate if effect should be drawn on top of everything or just on top of the hex if(areaEffect) be.position = BattleHex::INVALID; else be.position = destTile; owner->battleEffects.push_back(be); } else //there is nothing to play { endAnim(); return false; } } //battleEffects return true; }
bool CShootingAnimation::init() { if( !CAttackAnimation::checkInitialConditions() ) return false; const CStack * shooter = attackingStack; if(!shooter || myAnim->isDead()) { endAnim(); return false; } //reverse unit if necessary if (attackingStack && attackedStack && 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; } // 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 //maximal angle in radians between straight horizontal line and shooting line for which shot is considered to be straight (absoulte value) static const double straightAngle = 0.2; // Get further info about the shooter e.g. relative pos of projectile to unit. // If the creature id is 149 then it's a arrow tower which has no additional info so get the // actual arrow tower shooter instead. const CCreature *shooterInfo = shooter->getCreature(); if (shooterInfo->idNumber == CreatureID::ARROW_TOWERS) { int creID = owner->siegeH->town->town->clientInfo.siegeShooter; shooterInfo = CGI->creh->creatures[creID]; } ProjectileInfo spi; spi.shotDone = false; spi.creID = shooter->getCreature()->idNumber; spi.stackID = shooter->ID; // reverse if creature is facing right OR this is non-existing stack that is not tower (war machines) spi.reverse = attackingStack ? !owner->creDir[attackingStack->ID] : shooter->getCreature()->idNumber != CreatureID::ARROW_TOWERS ; spi.step = 0; spi.frameNum = 0; 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[spi.stackID]->pos.topLeft(); //xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner); destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner); // to properly translate coordinates when shooter is rotated int multiplier = spi.reverse ? -1 : 1; double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x)); if(shooter->position < dest) projectileAngle = -projectileAngle; // Calculate projectile start position. Offsets are read out of the CRANIM.TXT. if (projectileAngle > straightAngle) { //upper shot spi.x = fromPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier; spi.y = fromPos.y + 265 + shooterInfo->animation.upperRightMissleOffsetY; } else if (projectileAngle < -straightAngle) { //lower shot spi.x = fromPos.x + 222 + ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier; spi.y = fromPos.y + 265 + shooterInfo->animation.lowerRightMissleOffsetY; } else { //straight shot spi.x = fromPos.x + 222 + ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier; spi.y = fromPos.y + 265 + shooterInfo->animation.rightMissleOffsetY; } destPos += Point(225, 225); // recalculate angle taking in account offsets //projectileAngle = atan2(fabs(destPos.y - spi.y), fabs(destPos.x - spi.x)); //if(shooter->position < dest) // projectileAngle = -projectileAngle; if (attackedStack) { double animSpeed = AnimationControls::getProjectileSpeed(); // flight speed of projectile spi.lastStep = static_cast<int>(sqrt(static_cast<double>((destPos.x - spi.x) * (destPos.x - spi.x) + (destPos.y - spi.y) * (destPos.y - spi.y))) / animSpeed); if(spi.lastStep == 0) spi.lastStep = 1; spi.dx = (destPos.x - spi.x) / spi.lastStep; spi.dy = (destPos.y - spi.y) / spi.lastStep; } else { // Catapult attack spi.catapultInfo.reset(new CatapultProjectileInfo(Point(spi.x, spi.y), destPos)); double animSpeed = AnimationControls::getProjectileSpeed() / 10; spi.lastStep = abs((destPos.x - spi.x) / animSpeed); spi.dx = animSpeed; spi.dy = 0; SDL_Surface * img = owner->idToProjectile[spi.creID]->ourImages[0].bitmap; // Add explosion anim Point animPos(destPos.x - 126 + img->w / 2, destPos.y - 105 + img->h / 2); owner->addNewAnim( new CSpellEffectAnimation(owner, catapultDamage ? "SGEXPL.DEF" : "CSGRCK.DEF", animPos.x, animPos.y)); } auto & angles = shooterInfo->animation.missleFrameAngles; double pi = boost::math::constants::pi<double>(); // only frames below maxFrame are usable: anything higher is either no present or we don't know when it should be used size_t maxFrame = std::min<size_t>(angles.size(), owner->idToProjectile[spi.creID]->ourImages.size()); assert(maxFrame > 0); // values in angles array indicate position from which this frame was rendered, in degrees. // find frame that has closest angle to one that we need for this shot size_t bestID = 0; double bestDiff = fabs( angles[0] / 180 * pi - projectileAngle ); for (size_t i=1; i<maxFrame; i++) { double currentDiff = fabs( angles[i] / 180 * pi - projectileAngle ); if (currentDiff < bestDiff) { bestID = i; bestDiff = currentDiff; } } spi.frameNum = bestID; // Set projectile animation start delay which is specified in frames spi.animStartDelay = shooterInfo->animation.attackClimaxFrame; owner->projectiles.push_back(spi); //attack animation shooting = true; if(projectileAngle > straightAngle) //upper shot group = CCreatureAnim::SHOOT_UP; else if(projectileAngle < -straightAngle) //lower shot group = CCreatureAnim::SHOOT_DOWN; else //straight shot group = CCreatureAnim::SHOOT_FRONT; return true; }
bool CMovementAnimation::init() { if( !isEarliest(false) ) return false; if(!stack || myAnim->isDead()) { endAnim(); return false; } if(owner->creAnims[stack->ID]->framesInGroup(CCreatureAnim::MOVING) == 0 || stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1))) { //no movement or teleport, end immediately endAnim(); return false; } //reverse unit if necessary if(shouldRotate()) { // it seems that H3 does NOT plays full rotation animation here in most situations // Logical since it takes quite a lot of time if (curentMoveIndex == 0) // full rotation only for moving towards first tile. { owner->addNewAnim(new CReverseAnimation(owner, stack, oldPos, true)); return false; } else { CReverseAnimation::rotateStack(owner, stack, oldPos); } } if(myAnim->getType() != CCreatureAnim::MOVING) { myAnim->setType(CCreatureAnim::MOVING); } if (owner->moveSoundHander == -1) { owner->moveSoundHander = CCS->soundh->playSound(battle_sound(stack->getCreature(), move), -1); } Point begPosition = CClickableHex::getXYUnitAnim(oldPos, stack, owner); Point endPosition = CClickableHex::getXYUnitAnim(nextHex, stack, owner); timeToMove = AnimationControls::getMovementDuration(stack->getCreature()); begX = begPosition.x; begY = begPosition.y; progress = 0; distanceX = endPosition.x - begPosition.x; distanceY = endPosition.y - begPosition.y; if (stack->hasBonus(Selector::type(Bonus::FLYING))) { float distance = sqrt(distanceX * distanceX + distanceY * distanceY); timeToMove *= AnimationControls::getFlightDistance(stack->getCreature()) / distance; } return true; }
bool CMeleeAttackAnimation::init() { if( !CAttackAnimation::checkInitialConditions() ) return false; if(!attackingStack || myAnim->isDead()) { endAnim(); return false; } bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, dest, owner->creDir[stack->ID], attackedStack->doubleWide(), owner->creDir[attackedStack->ID]); 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 && owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID]) return false; //reversed 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}; int revShiftattacker = (attackingStack->attackerOwned ? -1 : 1); int mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn, dest); if(mutPos == -1 && attackingStack->doubleWide()) { mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, attackedStack->position); } if (mutPos == -1 && attackedStack->doubleWide()) { mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn, attackedStack->occupiedHex()); } if (mutPos == -1 && attackedStack->doubleWide() && attackingStack->doubleWide()) { mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, attackedStack->occupiedHex()); } switch(mutPos) //attack direction { case 0: case 1: case 2: case 3: case 4: case 5: group = mutPosToGroup[mutPos]; break; default: logGlobal->errorStream()<<"Critical Error! Wrong dest in stackAttacking! dest: "<<dest<<" attacking stack pos: "<<attackingStackPosBeforeReturn<<" mutual pos: "<<mutPos; group = CCreatureAnim::ATTACK_FRONT; break; } return true; }
void CDummyAnimation::nextFrame() { counter++; if(counter > howMany) endAnim(); }
void HWindow::closeWindow(){ setTocuhEnable(false); endAnim(); }