mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-24 08:32:34 +02:00
Implemented ray-like projectiles for shooters
- Added missing support for ray-like projectiles - Archmages, Evil Eyes and Beholders now use ray for shooting - New method to draw 1 pixel-wide line with color gradient at arbitrary angle - fixed incorrect attackClimaxFrame field for Archmages
This commit is contained in:
parent
e4f3d2a685
commit
6678a747bb
@ -785,22 +785,25 @@ bool CShootingAnimation::init()
|
||||
if (projectileAngle > straightAngle)
|
||||
{
|
||||
//upper shot
|
||||
spi.x = fromPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier;
|
||||
spi.y = fromPos.y + 265 + shooterInfo->animation.upperRightMissleOffsetY;
|
||||
spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier;
|
||||
spi.y0 = 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;
|
||||
spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier;
|
||||
spi.y0 = 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;
|
||||
spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier;
|
||||
spi.y0 = fromPos.y + 265 + shooterInfo->animation.rightMissleOffsetY;
|
||||
}
|
||||
|
||||
spi.x = spi.x0;
|
||||
spi.y = spi.y0;
|
||||
|
||||
destPos += Point(225, 225);
|
||||
|
||||
// recalculate angle taking in account offsets
|
||||
@ -837,30 +840,42 @@ bool CShootingAnimation::init()
|
||||
}
|
||||
double pi = boost::math::constants::pi<double>();
|
||||
|
||||
if (owner->idToProjectile.count(spi.creID) == 0) //in some cases (known one: hero grants shooter bonus to unit) the shooter stack's projectile may not be properly initialized
|
||||
//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) == 0 && owner->idToRay.count(spi.creID) == 0)
|
||||
owner->initStackProjectile(shooter);
|
||||
|
||||
// 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++)
|
||||
if (owner->idToProjectile.count(spi.creID) != 0)
|
||||
{
|
||||
double currentDiff = fabs( angles[i] / 180 * pi - projectileAngle );
|
||||
if (currentDiff < bestDiff)
|
||||
{
|
||||
bestID = i;
|
||||
bestDiff = currentDiff;
|
||||
}
|
||||
}
|
||||
// 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));
|
||||
|
||||
spi.frameNum = static_cast<int>(bestID);
|
||||
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) != 0)
|
||||
{
|
||||
// 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;
|
||||
|
@ -189,6 +189,7 @@ public:
|
||||
/// 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
|
||||
|
@ -1020,15 +1020,22 @@ void CBattleInterface::initStackProjectile(const CStack * stack)
|
||||
else
|
||||
creature = stack->getCreature();
|
||||
|
||||
std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(creature->animation.projectileImageName);
|
||||
projectile->preload();
|
||||
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");
|
||||
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
|
||||
projectile->createFlippedGroup(0, 1);
|
||||
|
||||
idToProjectile[stack->getCreature()->idNumber] = projectile;
|
||||
{
|
||||
idToRay[stack->getCreature()->idNumber] = creature->animation.projectileRay;
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleInterface::stackRemoved(uint32_t stackID)
|
||||
@ -3206,18 +3213,58 @@ void CBattleInterface::showProjectiles(SDL_Surface *to)
|
||||
continue; // wait...
|
||||
}
|
||||
|
||||
size_t group = it->reverse ? 1 : 0;
|
||||
auto image = idToProjectile[it->creID]->getImage(it->frameNum, group, true);
|
||||
|
||||
if(image)
|
||||
if ( idToProjectile.count(it->creID) != 0)
|
||||
{
|
||||
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);
|
||||
size_t group = it->reverse ? 1 : 0;
|
||||
auto image = idToProjectile[it->creID]->getImage(it->frameNum, group, true);
|
||||
|
||||
image->draw(to, &dst, nullptr);
|
||||
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) != 0)
|
||||
{
|
||||
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
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "CBattleAnimations.h"
|
||||
|
||||
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/battle/CBattleInfoCallback.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
@ -148,6 +149,7 @@ private:
|
||||
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;
|
||||
|
@ -361,6 +361,74 @@ void CSDL_Ext::update(SDL_Surface * what)
|
||||
if(0 !=SDL_UpdateTexture(screenTexture, nullptr, what->pixels, what->pitch))
|
||||
logGlobal->error("%s SDL_UpdateTexture %s", __FUNCTION__, SDL_GetError());
|
||||
}
|
||||
|
||||
uint8_t lerp(uint8_t a, uint8_t b, float f)
|
||||
{
|
||||
return a + std::round((b-a)*f);
|
||||
}
|
||||
|
||||
static void drawLineX(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2)
|
||||
{
|
||||
for(int x = x1; x <= x2; x++)
|
||||
{
|
||||
float f = float(x - x1) / float(x2 - x1);
|
||||
int y = y1 + std::round((y2-y1)*f);
|
||||
|
||||
uint8_t r = lerp(color1.r, color2.r, f);
|
||||
uint8_t g = lerp(color1.g, color2.g, f);
|
||||
uint8_t b = lerp(color1.b, color2.b, f);
|
||||
uint8_t a = lerp(color1.a, color2.a, f);
|
||||
|
||||
Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y);
|
||||
ColorPutter<4, 0>::PutColor(p, r,g,b,a);
|
||||
}
|
||||
}
|
||||
|
||||
static void drawLineY(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2)
|
||||
{
|
||||
for(int y = y1; y <= y2; y++)
|
||||
{
|
||||
float f = float(y - y1) / float(y2 - y1);
|
||||
int x = x1 + std::round((x2-x1)*f);
|
||||
|
||||
uint8_t r = lerp(color1.r, color2.r, f);
|
||||
uint8_t g = lerp(color1.g, color2.g, f);
|
||||
uint8_t b = lerp(color1.b, color2.b, f);
|
||||
uint8_t a = lerp(color1.a, color2.a, f);
|
||||
|
||||
Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y);
|
||||
ColorPutter<4, 0>::PutColor(p, r,g,b,a);
|
||||
}
|
||||
}
|
||||
|
||||
void CSDL_Ext::drawLine(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2)
|
||||
{
|
||||
int width = std::abs(x1-x2);
|
||||
int height = std::abs(y1-y2);
|
||||
|
||||
if ( width == 0 && height == 0)
|
||||
{
|
||||
Uint8 *p = CSDL_Ext::getPxPtr(sur, x1, y1);
|
||||
ColorPutter<4, 0>::PutColorAlpha(p, color1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (width > height)
|
||||
{
|
||||
if ( x1 < x2)
|
||||
drawLineX(sur, x1,y1,x2,y2, color1, color2);
|
||||
else
|
||||
drawLineX(sur, x2,y2,x1,y1, color1, color2);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( y1 < y2)
|
||||
drawLineY(sur, x1,y1,x2,y2, color1, color2);
|
||||
else
|
||||
drawLineY(sur, x2,y2,x1,y1, color1, color2);
|
||||
}
|
||||
}
|
||||
|
||||
void CSDL_Ext::drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const int3 &color)
|
||||
{
|
||||
for(int i = 0; i < w; i++)
|
||||
|
@ -236,6 +236,7 @@ namespace CSDL_Ext
|
||||
SDL_Color makeColor(ui8 r, ui8 g, ui8 b, ui8 a);
|
||||
|
||||
void update(SDL_Surface * what = screen); //updates whole surface (default - main screen)
|
||||
void drawLine(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2);
|
||||
void drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const int3 &color);
|
||||
void drawBorder(SDL_Surface * sur, const SDL_Rect &r, const int3 &color);
|
||||
void drawDashedBorder(SDL_Surface * sur, const Rect &r, const int3 &color);
|
||||
|
@ -135,7 +135,14 @@
|
||||
"animation": "CBEHOL.DEF",
|
||||
"missile" :
|
||||
{
|
||||
"projectile": "SMBALX.DEF"
|
||||
"ray" :
|
||||
[
|
||||
{ "start" : [ 160, 160, 160, 255 ], "end" : [ 160, 160, 160, 64 ] },
|
||||
{ "start" : [ 192, 192, 192, 255 ], "end" : [ 192, 192, 192, 128 ] },
|
||||
{ "start" : [ 224, 224, 224, 255 ], "end" : [ 224, 224, 224, 255 ] },
|
||||
{ "start" : [ 192, 192, 192, 255 ], "end" : [ 192, 192, 192, 128 ] },
|
||||
{ "start" : [ 160, 160, 160, 255 ], "end" : [ 160, 160, 160, 64 ] }
|
||||
]
|
||||
}
|
||||
},
|
||||
"sound" :
|
||||
@ -158,7 +165,14 @@
|
||||
"animation": "CEVEYE.DEF",
|
||||
"missile" :
|
||||
{
|
||||
"projectile": "SMBALX.DEF"
|
||||
"ray" :
|
||||
[
|
||||
{ "start" : [ 160, 160, 160, 255 ], "end" : [ 160, 160, 160, 64 ] },
|
||||
{ "start" : [ 192, 192, 192, 255 ], "end" : [ 192, 192, 192, 128 ] },
|
||||
{ "start" : [ 224, 224, 224, 255 ], "end" : [ 224, 224, 224, 255 ] },
|
||||
{ "start" : [ 192, 192, 192, 255 ], "end" : [ 192, 192, 192, 128 ] },
|
||||
{ "start" : [ 160, 160, 160, 255 ], "end" : [ 160, 160, 160, 64 ] }
|
||||
]
|
||||
}
|
||||
},
|
||||
"sound" :
|
||||
|
@ -208,7 +208,15 @@
|
||||
"animation": "CAMAGE.DEF",
|
||||
"missile" :
|
||||
{
|
||||
"projectile": "PMAGEX.DEF"
|
||||
"attackClimaxFrame" : 8,
|
||||
"ray" :
|
||||
[
|
||||
{ "start" : [ 160, 192, 0, 255 ], "end" : [ 160, 192, 0, 64 ] },
|
||||
{ "start" : [ 128, 224, 128, 255 ], "end" : [ 128, 224, 128, 128 ] },
|
||||
{ "start" : [ 32, 176, 32, 255 ], "end" : [ 32, 176, 32, 255 ] },
|
||||
{ "start" : [ 128, 224, 128, 255 ], "end" : [ 128, 224, 128, 128 ] },
|
||||
{ "start" : [ 160, 192, 0, 255 ], "end" : [ 160, 192, 0, 64 ] }
|
||||
]
|
||||
}
|
||||
},
|
||||
"sound" :
|
||||
|
@ -217,7 +217,7 @@
|
||||
"missile": {
|
||||
"type":"object",
|
||||
"additionalProperties" : false,
|
||||
"required" : [ "projectile", "frameAngles", "offset", "attackClimaxFrame" ],
|
||||
"required" : [ "frameAngles", "offset", "attackClimaxFrame" ],
|
||||
"description": "Missile description for archers",
|
||||
"properties":{
|
||||
"projectile": {
|
||||
@ -225,6 +225,10 @@
|
||||
"description": "Path to projectile animation",
|
||||
"format" : "defFile"
|
||||
},
|
||||
"ray": {
|
||||
"type":"array",
|
||||
"description": "Colors of ray projectile animation"
|
||||
},
|
||||
"frameAngles": {
|
||||
"type":"array",
|
||||
"description": "Angles of missile images, should go from 90 to -90",
|
||||
|
@ -907,6 +907,23 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c
|
||||
|
||||
creature->animation.projectileImageName = config["graphics"]["missile"]["projectile"].String();
|
||||
|
||||
for(const JsonNode & value : config["graphics"]["missile"]["ray"].Vector())
|
||||
{
|
||||
CCreature::CreatureAnimation::RayColor color;
|
||||
|
||||
color.r1 = value["start"].Vector()[0].Integer();
|
||||
color.g1 = value["start"].Vector()[1].Integer();
|
||||
color.b1 = value["start"].Vector()[2].Integer();
|
||||
color.a1 = value["start"].Vector()[3].Integer();
|
||||
|
||||
color.r2 = value["end"].Vector()[0].Integer();
|
||||
color.g2 = value["end"].Vector()[1].Integer();
|
||||
color.b2 = value["end"].Vector()[2].Integer();
|
||||
color.a2 = value["end"].Vector()[3].Integer();
|
||||
|
||||
creature->animation.projectileRay.push_back(color);
|
||||
}
|
||||
|
||||
creature->special = config["special"].Bool() || config["disabled"].Bool();
|
||||
|
||||
const JsonNode & sounds = config["sound"];
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <vcmi/Creature.h>
|
||||
#include <vcmi/CreatureService.h>
|
||||
#include <int3.h>
|
||||
|
||||
#include "HeroBonus.h"
|
||||
#include "ConstTransitivePtr.h"
|
||||
@ -63,6 +64,16 @@ public:
|
||||
|
||||
struct CreatureAnimation
|
||||
{
|
||||
struct RayColor {
|
||||
uint8_t r1, g1, b1, a1;
|
||||
uint8_t r2, g2, b2, a2;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & r1 & g1 & b1 & a1 & r2 & g2 & b2 & a2;
|
||||
}
|
||||
};
|
||||
|
||||
double timeBetweenFidgets, idleAnimationTime,
|
||||
walkAnimationTime, attackAnimationTime, flightAnimationDistance;
|
||||
int upperRightMissleOffsetX, rightMissleOffsetX, lowerRightMissleOffsetX,
|
||||
@ -72,6 +83,7 @@ public:
|
||||
int troopCountLocationOffset, attackClimaxFrame;
|
||||
|
||||
std::string projectileImageName;
|
||||
std::vector<RayColor> projectileRay;
|
||||
//bool projectileSpin; //if true, appropriate projectile is spinning during flight
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
@ -91,6 +103,7 @@ public:
|
||||
h & troopCountLocationOffset;
|
||||
h & attackClimaxFrame;
|
||||
h & projectileImageName;
|
||||
h & projectileRay;
|
||||
}
|
||||
} animation;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user