mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-24 08:32:34 +02:00
Battle Interface refactoring: separated projectile handling into a
separate class (untested)
This commit is contained in:
parent
1893212abb
commit
bd1b2e4977
@ -5,6 +5,9 @@ set(client_SRCS
|
||||
battle/CBattleAnimations.cpp
|
||||
battle/CBattleInterfaceClasses.cpp
|
||||
battle/CBattleInterface.cpp
|
||||
battle/CBattleObstacleController.cpp
|
||||
battle/CBattleProjectileController.cpp
|
||||
battle/CBattleSiegeController.cpp
|
||||
battle/CCreatureAnimation.cpp
|
||||
|
||||
gui/CAnimation.cpp
|
||||
@ -78,6 +81,9 @@ set(client_HEADERS
|
||||
battle/CBattleAnimations.h
|
||||
battle/CBattleInterfaceClasses.h
|
||||
battle/CBattleInterface.h
|
||||
battle/CBattleObstacleController.h
|
||||
battle/CBattleProjectileController.h
|
||||
battle/CBattleSiegeController.h
|
||||
battle/CCreatureAnimation.h
|
||||
|
||||
gui/CAnimation.h
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "CBattleInterfaceClasses.h"
|
||||
#include "CBattleInterface.h"
|
||||
#include "CBattleProjectileController.h"
|
||||
#include "CCreatureAnimation.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
@ -196,15 +197,9 @@ bool CDefenceAnimation::init()
|
||||
}
|
||||
//unit reversed
|
||||
|
||||
if(rangedAttack && attacker != nullptr) //delay hit animation
|
||||
if(rangedAttack && attacker != nullptr && owner->projectilesController->hasActiveProjectile(attacker)) //delay hit animation
|
||||
{
|
||||
for(std::list<ProjectileInfo>::const_iterator it = owner->projectiles.begin(); it != owner->projectiles.end(); ++it)
|
||||
{
|
||||
if(it->creID == attacker->getCreature()->idNumber)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// synchronize animation with attacker, unless defending or attacked by shooter:
|
||||
@ -725,14 +720,10 @@ bool CShootingAnimation::init()
|
||||
return false;
|
||||
}
|
||||
|
||||
// opponent must face attacker ( = different directions) before he can be attacked
|
||||
//FIXME: this cause freeze
|
||||
|
||||
// if (attackingStack && attackedStack &&
|
||||
// owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
|
||||
// return false;
|
||||
|
||||
// Create the projectile animation
|
||||
// opponent must face attacker ( = different directions) before he can be attacked
|
||||
//if (attackingStack && attackedStack && owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
|
||||
// return false;
|
||||
|
||||
//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;
|
||||
@ -744,40 +735,31 @@ bool CShootingAnimation::init()
|
||||
|
||||
if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS)
|
||||
{
|
||||
int creID = owner->siegeH->town->town->clientInfo.siegeShooter;
|
||||
shooterInfo = CGI->creh->operator[](creID);
|
||||
CreatureID creID = owner->siegeH->town->town->clientInfo.siegeShooter;
|
||||
shooterInfo = CGI->creh->objects[creID];
|
||||
}
|
||||
if(!shooterInfo->animation.missleFrameAngles.size())
|
||||
logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead..."
|
||||
, shooterInfo->nameSing);
|
||||
|
||||
auto & angles = shooterInfo->animation.missleFrameAngles.size()
|
||||
? shooterInfo->animation.missleFrameAngles
|
||||
: CGI->creh->operator[](CreatureID::ARCHER)->animation.missleFrameAngles;
|
||||
|
||||
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 shooterPos;
|
||||
Point shotPos;
|
||||
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();
|
||||
shooterPos = owner->creAnims[shooter->ID]->pos.topLeft();
|
||||
//xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner);
|
||||
|
||||
destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner);
|
||||
destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner) + Point(225, 225);
|
||||
|
||||
// to properly translate coordinates when shooter is rotated
|
||||
int multiplier = spi.reverse ? -1 : 1;
|
||||
int multiplier = 0;
|
||||
if (shooter)
|
||||
multiplier = owner->creDir[shooter->ID] ? 1 : -1;
|
||||
else
|
||||
{
|
||||
assert(false); // unreachable?
|
||||
multiplier = shooter->getCreature()->idNumber == CreatureID::ARROW_TOWERS ? -1 : 1;
|
||||
}
|
||||
|
||||
double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x));
|
||||
double projectileAngle = atan2(fabs((double)destPos.y - shooterPos.y), fabs((double)destPos.x - shooterPos.x));
|
||||
if(shooter->getPosition() < dest)
|
||||
projectileAngle = -projectileAngle;
|
||||
|
||||
@ -785,103 +767,23 @@ bool CShootingAnimation::init()
|
||||
if (projectileAngle > straightAngle)
|
||||
{
|
||||
//upper shot
|
||||
spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier;
|
||||
spi.y0 = fromPos.y + 265 + shooterInfo->animation.upperRightMissleOffsetY;
|
||||
shotPos.x = shooterPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier;
|
||||
shotPos.y = shooterPos.y + 265 + shooterInfo->animation.upperRightMissleOffsetY;
|
||||
}
|
||||
else if (projectileAngle < -straightAngle)
|
||||
{
|
||||
//lower shot
|
||||
spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier;
|
||||
spi.y0 = fromPos.y + 265 + shooterInfo->animation.lowerRightMissleOffsetY;
|
||||
shotPos.x = shooterPos.x + 222 + ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier;
|
||||
shotPos.y = shooterPos.y + 265 + shooterInfo->animation.lowerRightMissleOffsetY;
|
||||
}
|
||||
else
|
||||
{
|
||||
//straight shot
|
||||
spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier;
|
||||
spi.y0 = fromPos.y + 265 + shooterInfo->animation.rightMissleOffsetY;
|
||||
shotPos.x = shooterPos.x + 222 + ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier;
|
||||
shotPos.y = shooterPos.y + 265 + shooterInfo->animation.rightMissleOffsetY;
|
||||
}
|
||||
|
||||
spi.x = spi.x0;
|
||||
spi.y = spi.y0;
|
||||
|
||||
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
|
||||
double distanceSquared = (destPos.x - spi.x) * (destPos.x - spi.x) + (destPos.y - spi.y) * (destPos.y - spi.y);
|
||||
double distance = sqrt(distanceSquared);
|
||||
spi.lastStep = std::round(distance / 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((int)spi.x, (int)spi.y), destPos));
|
||||
|
||||
double animSpeed = AnimationControls::getProjectileSpeed() / 10;
|
||||
spi.lastStep = static_cast<int>(std::abs((destPos.x - spi.x) / animSpeed));
|
||||
spi.dx = animSpeed;
|
||||
spi.dy = 0;
|
||||
|
||||
auto img = owner->idToProjectile[spi.creID]->getImage(0);
|
||||
|
||||
// Add explosion anim
|
||||
Point animPos(destPos.x - 126 + img->width() / 2,
|
||||
destPos.y - 105 + img->height() / 2);
|
||||
|
||||
owner->addNewAnim( new CEffectAnimation(owner, catapultDamage ? "SGEXPL.DEF" : "CSGRCK.DEF", animPos.x, animPos.y));
|
||||
}
|
||||
double pi = boost::math::constants::pi<double>();
|
||||
|
||||
//in some cases (known one: hero grants shooter bonus to unit) the shooter stack's projectile may not be properly initialized
|
||||
if (!owner->idToProjectile.count(spi.creID) && !owner->idToRay.count(spi.creID))
|
||||
owner->initStackProjectile(shooter);
|
||||
|
||||
if (owner->idToProjectile.count(spi.creID))
|
||||
{
|
||||
// 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.at(spi.creID)->size(0));
|
||||
|
||||
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 = static_cast<int>(bestID);
|
||||
}
|
||||
else if (owner->idToRay.count(spi.creID))
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->error("Unable to find valid projectile for shooter %d", spi.creID);
|
||||
}
|
||||
|
||||
// Set projectile animation start delay which is specified in frames
|
||||
spi.animStartDelay = shooterInfo->animation.attackClimaxFrame;
|
||||
owner->projectiles.push_back(spi);
|
||||
owner->projectilesController->createProjectile(attackingStack, attackedStack, shotPos, destPos);
|
||||
|
||||
//attack animation
|
||||
|
||||
|
@ -186,23 +186,6 @@ public:
|
||||
virtual ~CReverseAnimation(){};
|
||||
};
|
||||
|
||||
/// Small struct which contains information about the position and the velocity of a projectile
|
||||
struct ProjectileInfo
|
||||
{
|
||||
double x0, y0; //initial position on the screen
|
||||
double x, y; //position on the screen
|
||||
double dx, dy; //change in position in one step
|
||||
int step, lastStep; //to know when finish showing this projectile
|
||||
int creID; //ID of creature that shot this projectile
|
||||
int stackID; //ID of stack
|
||||
int frameNum; //frame to display form projectile animation
|
||||
//bool spin; //if true, frameNum will be increased
|
||||
int animStartDelay; //frame of shooter animation when projectile should appear
|
||||
bool shotDone; // actual shot already done, projectile is flying
|
||||
bool reverse; //if true, projectile will be flipped by vertical asix
|
||||
std::shared_ptr<CatapultProjectileInfo> catapultInfo; // holds info about the parabolic trajectory of the cannon
|
||||
};
|
||||
|
||||
class CRangedAttackAnimation : public CAttackAnimation
|
||||
{
|
||||
public:
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "CBattleAnimations.h"
|
||||
#include "CBattleInterfaceClasses.h"
|
||||
#include "CCreatureAnimation.h"
|
||||
#include "CBattleProjectileController.h"
|
||||
|
||||
#include "../CBitmapHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
@ -113,6 +114,8 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
|
||||
{
|
||||
OBJ_CONSTRUCTION;
|
||||
|
||||
projectilesController.reset(new CBattleProjectileController(this));
|
||||
|
||||
if(spectatorInt)
|
||||
{
|
||||
curInt = spectatorInt;
|
||||
@ -1008,33 +1011,7 @@ void CBattleInterface::unitAdded(const CStack * stack)
|
||||
//loading projectiles for units
|
||||
if(stack->isShooter())
|
||||
{
|
||||
initStackProjectile(stack);
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleInterface::initStackProjectile(const CStack * stack)
|
||||
{
|
||||
const CCreature * creature;//creature whose shots should be loaded
|
||||
if(stack->getCreature()->idNumber == CreatureID::ARROW_TOWERS)
|
||||
creature = CGI->creh->objects[siegeH->town->town->clientInfo.siegeShooter];
|
||||
else
|
||||
creature = stack->getCreature();
|
||||
|
||||
if (creature->animation.projectileRay.empty())
|
||||
{
|
||||
std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(creature->animation.projectileImageName);
|
||||
projectile->preload();
|
||||
|
||||
if(projectile->size(1) != 0)
|
||||
logAnim->error("Expected empty group 1 in stack projectile");
|
||||
else
|
||||
projectile->createFlippedGroup(0, 1);
|
||||
|
||||
idToProjectile[stack->getCreature()->idNumber] = projectile;
|
||||
}
|
||||
else
|
||||
{
|
||||
idToRay[stack->getCreature()->idNumber] = creature->animation.projectileRay;
|
||||
projectilesController->initStackProjectile(stack);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2990,35 +2967,6 @@ void CBattleInterface::SiegeHelper::printPartOfWall(SDL_Surface *to, int what)
|
||||
}
|
||||
}
|
||||
|
||||
CatapultProjectileInfo::CatapultProjectileInfo(Point from, Point dest)
|
||||
{
|
||||
facA = 0.005; // seems to be constant
|
||||
|
||||
// system of 2 linear equations, solutions of which are missing coefficients
|
||||
// for quadratic equation a*x*x + b*x + c
|
||||
double eq[2][3] = {
|
||||
{ static_cast<double>(from.x), 1.0, from.y - facA*from.x*from.x },
|
||||
{ static_cast<double>(dest.x), 1.0, dest.y - facA*dest.x*dest.x }
|
||||
};
|
||||
|
||||
// solve system via determinants
|
||||
double det = eq[0][0] *eq[1][1] - eq[1][0] *eq[0][1];
|
||||
double detB = eq[0][2] *eq[1][1] - eq[1][2] *eq[0][1];
|
||||
double detC = eq[0][0] *eq[1][2] - eq[1][0] *eq[0][2];
|
||||
|
||||
facB = detB / det;
|
||||
facC = detC / det;
|
||||
|
||||
// make sure that parabola is correct e.g. passes through from and dest
|
||||
assert(fabs(calculateY(from.x) - from.y) < 1.0);
|
||||
assert(fabs(calculateY(dest.x) - dest.y) < 1.0);
|
||||
}
|
||||
|
||||
double CatapultProjectileInfo::calculateY(double x)
|
||||
{
|
||||
return facA *pow(x, 2.0) + facB *x + facC;
|
||||
}
|
||||
|
||||
void CBattleInterface::showAll(SDL_Surface *to)
|
||||
{
|
||||
show(to);
|
||||
@ -3036,7 +2984,7 @@ void CBattleInterface::show(SDL_Surface *to)
|
||||
|
||||
showBackground(to);
|
||||
showBattlefieldObjects(to);
|
||||
showProjectiles(to);
|
||||
projectilesController->showProjectiles(to);
|
||||
|
||||
if(battleActionsStarted)
|
||||
updateBattleAnimations();
|
||||
@ -3191,116 +3139,6 @@ void CBattleInterface::showHighlightedHex(SDL_Surface *to, BattleHex hex, bool d
|
||||
CSDL_Ext::blit8bppAlphaTo24bpp(cellBorder, nullptr, to, &temp_rect); //redraw border to make it light green instead of shaded
|
||||
}
|
||||
|
||||
void CBattleInterface::showProjectiles(SDL_Surface *to)
|
||||
{
|
||||
assert(to);
|
||||
|
||||
std::list< std::list<ProjectileInfo>::iterator > toBeDeleted;
|
||||
for (auto it = projectiles.begin(); it!=projectiles.end(); ++it)
|
||||
{
|
||||
// Check if projectile is already visible (shooter animation did the shot)
|
||||
if (!it->shotDone)
|
||||
{
|
||||
// frame we're waiting for is reached OR animation has already finished
|
||||
if (creAnims[it->stackID]->getCurrentFrame() >= it->animStartDelay ||
|
||||
creAnims[it->stackID]->isShooting() == false)
|
||||
{
|
||||
//at this point projectile should become visible
|
||||
creAnims[it->stackID]->pause(); // pause animation
|
||||
it->shotDone = true;
|
||||
}
|
||||
else
|
||||
continue; // wait...
|
||||
}
|
||||
|
||||
if (idToProjectile.count(it->creID))
|
||||
{
|
||||
size_t group = it->reverse ? 1 : 0;
|
||||
auto image = idToProjectile[it->creID]->getImage(it->frameNum, group, true);
|
||||
|
||||
if(image)
|
||||
{
|
||||
SDL_Rect dst;
|
||||
dst.h = image->height();
|
||||
dst.w = image->width();
|
||||
dst.x = static_cast<int>(it->x - dst.w / 2);
|
||||
dst.y = static_cast<int>(it->y - dst.h / 2);
|
||||
|
||||
image->draw(to, &dst, nullptr);
|
||||
}
|
||||
}
|
||||
if (idToRay.count(it->creID))
|
||||
{
|
||||
auto const & ray = idToRay[it->creID];
|
||||
|
||||
if (std::abs(it->dx) > std::abs(it->dy)) // draw in horizontal axis
|
||||
{
|
||||
int y1 = it->y0 - ray.size() / 2;
|
||||
int y2 = it->y - ray.size() / 2;
|
||||
|
||||
int x1 = it->x0;
|
||||
int x2 = it->x;
|
||||
|
||||
for (size_t i = 0; i < ray.size(); ++i)
|
||||
{
|
||||
SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1};
|
||||
SDL_Color endColor { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2};
|
||||
|
||||
CSDL_Ext::drawLine(to, x1, y1 + i, x2, y2 + i, beginColor, endColor);
|
||||
}
|
||||
}
|
||||
else // draw in vertical axis
|
||||
{
|
||||
int x1 = it->x0 - ray.size() / 2;
|
||||
int x2 = it->x - ray.size() / 2;
|
||||
|
||||
int y1 = it->y0;
|
||||
int y2 = it->y;
|
||||
|
||||
for (size_t i = 0; i < ray.size(); ++i)
|
||||
{
|
||||
SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1};
|
||||
SDL_Color endColor { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2};
|
||||
|
||||
CSDL_Ext::drawLine(to, x1 + i, y1, x2 + i, y2, beginColor, endColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update projectile
|
||||
++it->step;
|
||||
if (it->step > it->lastStep)
|
||||
{
|
||||
toBeDeleted.insert(toBeDeleted.end(), it);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (it->catapultInfo)
|
||||
{
|
||||
// Parabolic shot of the trajectory, as follows: f(x) = ax^2 + bx + c
|
||||
it->x += it->dx;
|
||||
it->y = it->catapultInfo->calculateY(it->x);
|
||||
|
||||
++(it->frameNum);
|
||||
it->frameNum %= idToProjectile[it->creID]->size(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal projectile, just add the calculated "deltas" to the x and y positions.
|
||||
it->x += it->dx;
|
||||
it->y += it->dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto & elem : toBeDeleted)
|
||||
{
|
||||
// resume animation
|
||||
creAnims[elem->stackID]->play();
|
||||
projectiles.erase(elem);
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleInterface::showBattlefieldObjects(SDL_Surface *to)
|
||||
{
|
||||
auto showHexEntry = [&](BattleObjectsByHex::HexData & hex)
|
||||
|
@ -47,7 +47,6 @@ class CCallback;
|
||||
class CButton;
|
||||
class CToggleButton;
|
||||
class CToggleGroup;
|
||||
struct CatapultProjectileInfo;
|
||||
class CBattleAnimation;
|
||||
class CBattleHero;
|
||||
class CBattleConsole;
|
||||
@ -55,11 +54,12 @@ class CBattleResultWindow;
|
||||
class CStackQueue;
|
||||
class CPlayerInterface;
|
||||
class CCreatureAnimation;
|
||||
struct ProjectileInfo;
|
||||
class CClickableHex;
|
||||
class CAnimation;
|
||||
class IImage;
|
||||
|
||||
class CBattleProjectileController;
|
||||
|
||||
/// Small struct which contains information about the id of the attacked stack, the damage dealt,...
|
||||
struct StackAttackedInfo
|
||||
{
|
||||
@ -104,16 +104,6 @@ struct BattleObjectsByHex
|
||||
std::array<HexData, GameConstants::BFIELD_SIZE> hex;
|
||||
};
|
||||
|
||||
/// Small struct which is needed for drawing the parabolic trajectory of the catapult cannon
|
||||
struct CatapultProjectileInfo
|
||||
{
|
||||
CatapultProjectileInfo(Point from, Point dest);
|
||||
|
||||
double facA, facB, facC;
|
||||
|
||||
double calculateY(double x);
|
||||
};
|
||||
|
||||
enum class MouseHoveredHexContext
|
||||
{
|
||||
UNOCCUPIED_HEX,
|
||||
@ -148,9 +138,6 @@ private:
|
||||
const CGHeroInstance *attackingHeroInstance, *defendingHeroInstance;
|
||||
std::map<int32_t, std::shared_ptr<CCreatureAnimation>> creAnims; //animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
|
||||
|
||||
std::map<int, std::shared_ptr<CAnimation>> idToProjectile;
|
||||
std::map<int, std::vector<CCreature::CreatureAnimation::RayColor>> idToRay;
|
||||
|
||||
std::map<std::string, std::shared_ptr<CAnimation>> animationsCache;
|
||||
std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;
|
||||
|
||||
@ -197,7 +184,6 @@ private:
|
||||
//force active stack to cast a spell if possible
|
||||
void enterCreatureCastingMode();
|
||||
|
||||
std::list<ProjectileInfo> projectiles; //projectiles flying on battlefield
|
||||
void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
|
||||
void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
|
||||
|
||||
@ -269,7 +255,6 @@ private:
|
||||
void showPiecesOfWall(SDL_Surface *to, std::vector<int> pieces);
|
||||
|
||||
void showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects);
|
||||
void showProjectiles(SDL_Surface *to);
|
||||
|
||||
BattleObjectsByHex sortObjectsByHex();
|
||||
void updateBattleAnimations();
|
||||
@ -283,6 +268,8 @@ private:
|
||||
|
||||
void setHeroAnimation(ui8 side, int phase);
|
||||
public:
|
||||
std::unique_ptr<CBattleProjectileController> projectilesController;
|
||||
|
||||
static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
|
||||
static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
|
||||
|
||||
@ -388,7 +375,6 @@ public:
|
||||
|
||||
void gateStateChanged(const EGateState state);
|
||||
|
||||
void initStackProjectile(const CStack * stack);
|
||||
|
||||
const CGHeroInstance *currentHero() const;
|
||||
InfoAboutHero enemyHero() const;
|
||||
@ -410,4 +396,5 @@ public:
|
||||
friend class CShootingAnimation;
|
||||
friend class CCastAnimation;
|
||||
friend class CClickableHex;
|
||||
friend class CBattleProjectileController;
|
||||
};
|
||||
|
12
client/battle/CBattleObstacleController.cpp
Normal file
12
client/battle/CBattleObstacleController.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* CBattleObstacleController.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "CBattleObstacleController.h"
|
||||
|
15
client/battle/CBattleObstacleController.h
Normal file
15
client/battle/CBattleObstacleController.h
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* CBattleObstacleController.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class CBattleObstacleController
|
||||
{
|
||||
|
||||
};
|
320
client/battle/CBattleProjectileController.cpp
Normal file
320
client/battle/CBattleProjectileController.cpp
Normal file
@ -0,0 +1,320 @@
|
||||
/*
|
||||
* CBattleProjectileController.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "CBattleProjectileController.h"
|
||||
#include "../gui/Geometries.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
#include "CBattleInterface.h"
|
||||
#include "CCreatureAnimation.h"
|
||||
|
||||
CatapultProjectileInfo::CatapultProjectileInfo(const Point &from, const Point &dest)
|
||||
{
|
||||
facA = 0.005; // seems to be constant
|
||||
|
||||
// system of 2 linear equations, solutions of which are missing coefficients
|
||||
// for quadratic equation a*x*x + b*x + c
|
||||
double eq[2][3] = {
|
||||
{ static_cast<double>(from.x), 1.0, from.y - facA*from.x*from.x },
|
||||
{ static_cast<double>(dest.x), 1.0, dest.y - facA*dest.x*dest.x }
|
||||
};
|
||||
|
||||
// solve system via determinants
|
||||
double det = eq[0][0] *eq[1][1] - eq[1][0] *eq[0][1];
|
||||
double detB = eq[0][2] *eq[1][1] - eq[1][2] *eq[0][1];
|
||||
double detC = eq[0][0] *eq[1][2] - eq[1][0] *eq[0][2];
|
||||
|
||||
facB = detB / det;
|
||||
facC = detC / det;
|
||||
|
||||
// make sure that parabola is correct e.g. passes through from and dest
|
||||
assert(fabs(calculateY(from.x) - from.y) < 1.0);
|
||||
assert(fabs(calculateY(dest.x) - dest.y) < 1.0);
|
||||
}
|
||||
|
||||
double CatapultProjectileInfo::calculateY(double x)
|
||||
{
|
||||
return facA *pow(x, 2.0) + facB *x + facC;
|
||||
}
|
||||
|
||||
CBattleProjectileController::CBattleProjectileController(CBattleInterface * owner):
|
||||
owner(owner)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void CBattleProjectileController::initStackProjectile(const CStack * stack)
|
||||
{
|
||||
const CCreature * creature;//creature whose shots should be loaded
|
||||
if(stack->getCreature()->idNumber == CreatureID::ARROW_TOWERS)
|
||||
creature = CGI->creh->objects[owner->siegeH->town->town->clientInfo.siegeShooter];
|
||||
else
|
||||
creature = stack->getCreature();
|
||||
|
||||
if (creature->animation.projectileRay.empty())
|
||||
{
|
||||
std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(creature->animation.projectileImageName);
|
||||
projectile->preload();
|
||||
|
||||
if(projectile->size(1) != 0)
|
||||
logAnim->error("Expected empty group 1 in stack projectile");
|
||||
else
|
||||
projectile->createFlippedGroup(0, 1);
|
||||
|
||||
idToProjectile[stack->getCreature()->idNumber] = projectile;
|
||||
}
|
||||
else
|
||||
{
|
||||
idToRay[stack->getCreature()->idNumber] = creature->animation.projectileRay;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CBattleProjectileController::showProjectiles(SDL_Surface *to)
|
||||
{
|
||||
assert(to);
|
||||
|
||||
std::list< std::list<ProjectileInfo>::iterator > toBeDeleted;
|
||||
for (auto it = projectiles.begin(); it!=projectiles.end(); ++it)
|
||||
{
|
||||
// Check if projectile is already visible (shooter animation did the shot)
|
||||
if (!it->shotDone)
|
||||
{
|
||||
// frame we're waiting for is reached OR animation has already finished
|
||||
if (owner->creAnims[it->stackID]->getCurrentFrame() >= it->animStartDelay ||
|
||||
owner->creAnims[it->stackID]->isShooting() == false)
|
||||
{
|
||||
//at this point projectile should become visible
|
||||
owner->creAnims[it->stackID]->pause(); // pause animation
|
||||
it->shotDone = true;
|
||||
}
|
||||
else
|
||||
continue; // wait...
|
||||
}
|
||||
|
||||
if (idToProjectile.count(it->creID))
|
||||
{
|
||||
size_t group = it->reverse ? 1 : 0;
|
||||
auto image = idToProjectile[it->creID]->getImage(it->frameNum, group, true);
|
||||
|
||||
if(image)
|
||||
{
|
||||
SDL_Rect dst;
|
||||
dst.h = image->height();
|
||||
dst.w = image->width();
|
||||
dst.x = static_cast<int>(it->x - dst.w / 2);
|
||||
dst.y = static_cast<int>(it->y - dst.h / 2);
|
||||
|
||||
image->draw(to, &dst, nullptr);
|
||||
}
|
||||
}
|
||||
if (idToRay.count(it->creID))
|
||||
{
|
||||
auto const & ray = idToRay[it->creID];
|
||||
|
||||
if (std::abs(it->dx) > std::abs(it->dy)) // draw in horizontal axis
|
||||
{
|
||||
int y1 = it->y0 - ray.size() / 2;
|
||||
int y2 = it->y - ray.size() / 2;
|
||||
|
||||
int x1 = it->x0;
|
||||
int x2 = it->x;
|
||||
|
||||
for (size_t i = 0; i < ray.size(); ++i)
|
||||
{
|
||||
SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1};
|
||||
SDL_Color endColor { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2};
|
||||
|
||||
CSDL_Ext::drawLine(to, x1, y1 + i, x2, y2 + i, beginColor, endColor);
|
||||
}
|
||||
}
|
||||
else // draw in vertical axis
|
||||
{
|
||||
int x1 = it->x0 - ray.size() / 2;
|
||||
int x2 = it->x - ray.size() / 2;
|
||||
|
||||
int y1 = it->y0;
|
||||
int y2 = it->y;
|
||||
|
||||
for (size_t i = 0; i < ray.size(); ++i)
|
||||
{
|
||||
SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1};
|
||||
SDL_Color endColor { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2};
|
||||
|
||||
CSDL_Ext::drawLine(to, x1 + i, y1, x2 + i, y2, beginColor, endColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update projectile
|
||||
++it->step;
|
||||
if (it->step > it->lastStep)
|
||||
{
|
||||
toBeDeleted.insert(toBeDeleted.end(), it);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (it->catapultInfo)
|
||||
{
|
||||
// Parabolic shot of the trajectory, as follows: f(x) = ax^2 + bx + c
|
||||
it->x += it->dx;
|
||||
it->y = it->catapultInfo->calculateY(it->x);
|
||||
|
||||
++(it->frameNum);
|
||||
it->frameNum %= idToProjectile[it->creID]->size(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal projectile, just add the calculated "deltas" to the x and y positions.
|
||||
it->x += it->dx;
|
||||
it->y += it->dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto & elem : toBeDeleted)
|
||||
{
|
||||
// resume animation
|
||||
owner->creAnims[elem->stackID]->play();
|
||||
projectiles.erase(elem);
|
||||
}
|
||||
}
|
||||
|
||||
bool CBattleProjectileController::hasActiveProjectile(const CStack * stack)
|
||||
{
|
||||
for(auto const & instance : projectiles)
|
||||
{
|
||||
if(instance.creID == stack->getCreature()->idNumber)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void CBattleProjectileController::createProjectile(const CStack * shooter, const CStack * target, Point from, Point dest)
|
||||
{
|
||||
// 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->operator[](creID);
|
||||
}
|
||||
if(!shooterInfo->animation.missleFrameAngles.size())
|
||||
logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead..."
|
||||
, shooterInfo->nameSing);
|
||||
|
||||
auto & angles = shooterInfo->animation.missleFrameAngles.size()
|
||||
? shooterInfo->animation.missleFrameAngles
|
||||
: CGI->creh->operator[](CreatureID::ARCHER)->animation.missleFrameAngles;
|
||||
|
||||
// recalculate angle taking in account offsets
|
||||
//projectileAngle = atan2(fabs(destPos.y - spi.y), fabs(destPos.x - spi.x));
|
||||
//if(shooter->position < dest)
|
||||
// projectileAngle = -projectileAngle;
|
||||
|
||||
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 = shooter ? !owner->creDir[shooter->ID] : shooter->getCreature()->idNumber != CreatureID::ARROW_TOWERS;
|
||||
|
||||
spi.step = 0;
|
||||
spi.frameNum = 0;
|
||||
|
||||
spi.x0 = from.x;
|
||||
spi.y0 = from.y;
|
||||
|
||||
spi.x = from.x;
|
||||
spi.y = from.y;
|
||||
|
||||
if (target)
|
||||
{
|
||||
double animSpeed = AnimationControls::getProjectileSpeed(); // flight speed of projectile
|
||||
double distanceSquared = (dest.x - spi.x) * (dest.x - spi.x) + (dest.y - spi.y) * (dest.y - spi.y);
|
||||
double distance = sqrt(distanceSquared);
|
||||
spi.lastStep = std::round(distance / animSpeed);
|
||||
if(spi.lastStep == 0)
|
||||
spi.lastStep = 1;
|
||||
spi.dx = (dest.x - spi.x) / spi.lastStep;
|
||||
spi.dy = (dest.y - spi.y) / spi.lastStep;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Catapult attack
|
||||
spi.catapultInfo.reset(new CatapultProjectileInfo(Point((int)spi.x, (int)spi.y), dest));
|
||||
|
||||
double animSpeed = AnimationControls::getProjectileSpeed() / 10;
|
||||
spi.lastStep = static_cast<int>(std::abs((dest.x - spi.x) / animSpeed));
|
||||
spi.dx = animSpeed;
|
||||
spi.dy = 0;
|
||||
|
||||
auto img = idToProjectile[spi.creID]->getImage(0);
|
||||
|
||||
// Add explosion anim
|
||||
Point animPos(dest.x - 126 + img->width() / 2,
|
||||
dest.y - 105 + img->height() / 2);
|
||||
|
||||
//owner->addNewAnim( new CEffectAnimation(owner, catapultDamage ? "SGEXPL.DEF" : "CSGRCK.DEF", animPos.x, animPos.y));
|
||||
}
|
||||
double pi = std::atan(1)*4;
|
||||
|
||||
//in some cases (known one: hero grants shooter bonus to unit) the shooter stack's projectile may not be properly initialized
|
||||
if (!idToProjectile.count(spi.creID) && !idToRay.count(spi.creID))
|
||||
initStackProjectile(shooter);
|
||||
|
||||
if (idToProjectile.count(spi.creID))
|
||||
{
|
||||
// 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(), idToProjectile.at(spi.creID)->size(0));
|
||||
|
||||
assert(maxFrame > 0);
|
||||
double projectileAngle = atan2(fabs((double)dest.y - from.y), fabs((double)dest.x - from.x));
|
||||
//if(shooter->getPosition() < dest)
|
||||
// projectileAngle = -projectileAngle;
|
||||
|
||||
// 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 = static_cast<int>(bestID);
|
||||
}
|
||||
else if (idToRay.count(spi.creID))
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->error("Unable to find valid projectile for shooter %d", spi.creID);
|
||||
}
|
||||
|
||||
// Set projectile animation start delay which is specified in frames
|
||||
spi.animStartDelay = shooterInfo->animation.attackClimaxFrame;
|
||||
projectiles.push_back(spi);
|
||||
}
|
65
client/battle/CBattleProjectileController.h
Normal file
65
client/battle/CBattleProjectileController.h
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* CBattleSiegeController.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
|
||||
struct Point;
|
||||
struct SDL_Surface;
|
||||
class CAnimation;
|
||||
class CStack;
|
||||
class CBattleInterface;
|
||||
|
||||
/// Small struct which is needed for drawing the parabolic trajectory of the catapult cannon
|
||||
struct CatapultProjectileInfo
|
||||
{
|
||||
CatapultProjectileInfo(const Point &from, const Point &dest);
|
||||
|
||||
double facA, facB, facC;
|
||||
|
||||
double calculateY(double x);
|
||||
};
|
||||
|
||||
/// Small struct which contains information about the position and the velocity of a projectile
|
||||
struct ProjectileInfo
|
||||
{
|
||||
double x0, y0; //initial position on the screen
|
||||
double x, y; //position on the screen
|
||||
double dx, dy; //change in position in one step
|
||||
int step, lastStep; //to know when finish showing this projectile
|
||||
int creID; //ID of creature that shot this projectile
|
||||
int stackID; //ID of stack
|
||||
int frameNum; //frame to display form projectile animation
|
||||
//bool spin; //if true, frameNum will be increased
|
||||
int animStartDelay; //frame of shooter animation when projectile should appear
|
||||
bool shotDone; // actual shot already done, projectile is flying
|
||||
bool reverse; //if true, projectile will be flipped by vertical asix
|
||||
std::shared_ptr<CatapultProjectileInfo> catapultInfo; // holds info about the parabolic trajectory of the cannon
|
||||
};
|
||||
|
||||
class CBattleProjectileController
|
||||
{
|
||||
CBattleInterface * owner;
|
||||
|
||||
std::map<int, std::shared_ptr<CAnimation>> idToProjectile;
|
||||
std::map<int, std::vector<CCreature::CreatureAnimation::RayColor>> idToRay;
|
||||
|
||||
std::list<ProjectileInfo> projectiles; //projectiles flying on battlefield
|
||||
|
||||
public:
|
||||
CBattleProjectileController(CBattleInterface * owner);
|
||||
|
||||
void showProjectiles(SDL_Surface *to);
|
||||
void initStackProjectile(const CStack * stack);
|
||||
|
||||
bool hasActiveProjectile(const CStack * stack);
|
||||
|
||||
void createProjectile(const CStack * shooter, const CStack * target, Point from, Point dest);
|
||||
};
|
12
client/battle/CBattleSiegeController.cpp
Normal file
12
client/battle/CBattleSiegeController.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* CBattleSiegeController.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "CBattleSiegeController.h"
|
||||
|
17
client/battle/CBattleSiegeController.h
Normal file
17
client/battle/CBattleSiegeController.h
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* CBattleObstacleController.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
class CBattleSiegeController
|
||||
{
|
||||
|
||||
};
|
@ -701,6 +701,10 @@ namespace VCMIDirs
|
||||
#ifdef VCMI_WINDOWS
|
||||
std::locale::global(boost::locale::generator().generate("en_US.UTF-8"));
|
||||
#endif
|
||||
|
||||
#ifdef VCMI_XDG
|
||||
setenv("LC_ALL", "C", 1);
|
||||
#endif
|
||||
boost::filesystem::path::imbue(std::locale());
|
||||
|
||||
singleton.init();
|
||||
|
Loading…
Reference in New Issue
Block a user