mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-28 08:48:48 +02:00
Implemented Bloodlust & Petrification effect
- ColorFilter is now in separate file - Moved lerp function into global.h - Bloodlust visuals mostly matches H3 - Petrify visual matches H3 - TODO: Adjust timing of all ColorFilter efects to match H3 - TODO: Petrify should pause stack animations - TODO: ColorFilter-powered effects should be configurable in Spell system
This commit is contained in:
parent
7e35a96055
commit
864990db13
7
Global.h
7
Global.h
@ -760,6 +760,13 @@ namespace vstd
|
||||
return v;
|
||||
}
|
||||
|
||||
//c++20 feature
|
||||
template<typename Arithmetic, typename Floating>
|
||||
Arithmetic lerp(const Arithmetic & a, const Arithmetic & b, const Floating & f)
|
||||
{
|
||||
return a + (b - a) * f;
|
||||
}
|
||||
|
||||
using boost::math::round;
|
||||
}
|
||||
using vstd::operator-=;
|
||||
|
@ -21,6 +21,7 @@ set(client_SRCS
|
||||
gui/CCursorHandler.cpp
|
||||
gui/CGuiHandler.cpp
|
||||
gui/CIntObject.cpp
|
||||
gui/ColorFilter.cpp
|
||||
gui/Fonts.cpp
|
||||
gui/Geometries.cpp
|
||||
gui/SDL_Extensions.cpp
|
||||
@ -104,6 +105,7 @@ set(client_HEADERS
|
||||
gui/Canvas.h
|
||||
gui/CCursorHandler.h
|
||||
gui/CGuiHandler.h
|
||||
gui/ColorFilter.h
|
||||
gui/CIntObject.h
|
||||
gui/Fonts.h
|
||||
gui/Geometries.h
|
||||
|
@ -18,7 +18,7 @@ CreatureCostBox::CreatureCostBox(Rect position, std::string titleText)
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
|
||||
type |= REDRAW_PARENT;
|
||||
pos = position + pos;
|
||||
pos = position + pos.topLeft();
|
||||
|
||||
title = std::make_shared<CLabel>(pos.w/2, 10, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, titleText);
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../gui/CCursorHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/SDL_Extensions.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/CStack.h"
|
||||
@ -409,7 +410,7 @@ void MovementAnimation::nextFrame()
|
||||
{
|
||||
// Sets the position of the creature animation sprites
|
||||
Point coords = owner.stacksController->getStackPositionAtHex(currentHex, stack);
|
||||
myAnim->pos = coords;
|
||||
myAnim->pos.moveTo(coords);
|
||||
|
||||
// true if creature haven't reached the final destination hex
|
||||
if ((curentMoveIndex + 1) < destTiles.size())
|
||||
@ -431,7 +432,7 @@ MovementAnimation::~MovementAnimation()
|
||||
{
|
||||
assert(stack);
|
||||
|
||||
myAnim->pos = owner.stacksController->getStackPositionAtHex(currentHex, stack);
|
||||
myAnim->pos.moveTo(owner.stacksController->getStackPositionAtHex(currentHex, stack));
|
||||
|
||||
if(owner.moveSoundHander != -1)
|
||||
{
|
||||
@ -560,7 +561,7 @@ void BattleStackAnimation::rotateStack(BattleHex hex)
|
||||
{
|
||||
setStackFacingRight(stack, !stackFacingRight(stack));
|
||||
|
||||
stackAnimation(stack)->pos = owner.stacksController->getStackPositionAtHex(hex, stack);
|
||||
stackAnimation(stack)->pos.moveTo(owner.stacksController->getStackPositionAtHex(hex, stack));
|
||||
}
|
||||
|
||||
void ReverseAnimation::setupSecondPart()
|
||||
@ -607,41 +608,109 @@ ResurrectionAnimation::ResurrectionAnimation(BattleInterface & owner, const CSta
|
||||
|
||||
}
|
||||
|
||||
bool FadingAnimation::init()
|
||||
bool ColorTransformAnimation::init()
|
||||
{
|
||||
logAnim->info("FadingAnimation::init: stack %s", stack->getName());
|
||||
//TODO: pause animation?
|
||||
return true;
|
||||
}
|
||||
|
||||
void FadingAnimation::nextFrame()
|
||||
void ColorTransformAnimation::nextFrame()
|
||||
{
|
||||
float elapsed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
|
||||
float fullTime = AnimationControls::getFadeInDuration();
|
||||
float delta = elapsed / fullTime;
|
||||
progress += delta;
|
||||
totalProgress += delta;
|
||||
|
||||
if (progress > 1.0f)
|
||||
progress = 1.0f;
|
||||
size_t index = 0;
|
||||
|
||||
uint8_t factor = stack->cloned ? 128 : 255;
|
||||
uint8_t blue = stack->cloned ? 128 : 0;
|
||||
uint8_t alpha = CSDL_Ext::lerp(from, dest, progress);
|
||||
while (index < timePoints.size() && timePoints[index] < totalProgress )
|
||||
++index;
|
||||
|
||||
ColorShifterRange shifterFade ({0, 0, blue, 0}, {factor, factor, 255, alpha});
|
||||
stackAnimation(stack)->shiftColor(&shifterFade);
|
||||
|
||||
if (progress == 1.0f)
|
||||
if (index == timePoints.size())
|
||||
{
|
||||
//end of animation. Apply ColorShifter using final values and die
|
||||
const auto & shifter = steps[index - 1];
|
||||
owner.stacksController->setStackColorFilter(shifter, stack, spell, false);
|
||||
delete this;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(index != 0);
|
||||
|
||||
const auto & prevShifter = steps[index - 1];
|
||||
const auto & nextShifter = steps[index];
|
||||
|
||||
float prevPoint = timePoints[index-1];
|
||||
float nextPoint = timePoints[index];
|
||||
float localProgress = totalProgress - prevPoint;
|
||||
float stepDuration = (nextPoint - prevPoint);
|
||||
float factor = localProgress / stepDuration;
|
||||
|
||||
auto shifter = ColorFilter::genInterpolated(prevShifter, nextShifter, factor);
|
||||
|
||||
owner.stacksController->setStackColorFilter(shifter, stack, spell, true);
|
||||
}
|
||||
|
||||
FadingAnimation::FadingAnimation(BattleInterface & owner, const CStack * _stack, uint8_t alphaFrom, uint8_t alphaDest):
|
||||
ColorTransformAnimation::ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const CSpell * spell):
|
||||
BattleStackAnimation(owner, _stack),
|
||||
from(alphaFrom),
|
||||
dest(alphaDest)
|
||||
spell(spell),
|
||||
totalProgress(0.f)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ColorTransformAnimation * ColorTransformAnimation::bloodlustAnimation(BattleInterface & owner, const CStack * stack, const CSpell * spell)
|
||||
{
|
||||
auto result = new ColorTransformAnimation(owner, stack, spell);
|
||||
|
||||
result->steps.push_back(ColorFilter::genEmptyShifter());
|
||||
result->steps.push_back(ColorFilter::genMuxerShifter( { 0.3, 0.0, 0.3, 0.4}, { 0, 1, 0, 0}, { 0, 0, 1, 0}, 1.f ));
|
||||
result->steps.push_back(ColorFilter::genMuxerShifter( { 0.3, 0.3, 0.3, 0.1}, { 0, 0.5, 0, 0}, { 0, 0.5, 0, 0}, 1.f ));
|
||||
result->steps.push_back(ColorFilter::genMuxerShifter( { 0.3, 0.0, 0.3, 0.4}, { 0, 1, 0, 0}, { 0, 0, 1, 0}, 1.f ));
|
||||
result->steps.push_back(ColorFilter::genEmptyShifter());
|
||||
|
||||
result->timePoints.push_back(0.0f);
|
||||
result->timePoints.push_back(0.2f);
|
||||
result->timePoints.push_back(0.4f);
|
||||
result->timePoints.push_back(0.6f);
|
||||
result->timePoints.push_back(0.8f);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ColorTransformAnimation * ColorTransformAnimation::cloneAnimation(BattleInterface & owner, const CStack * stack, const CSpell * spell)
|
||||
{
|
||||
auto result = new ColorTransformAnimation(owner, stack, spell);
|
||||
result->steps.push_back(ColorFilter::genRangeShifter( 0, 0, 0.5, 0.5, 0.5, 1.0));
|
||||
result->timePoints.push_back(0.f);
|
||||
return result;
|
||||
}
|
||||
|
||||
ColorTransformAnimation * ColorTransformAnimation::petrifyAnimation(BattleInterface & owner, const CStack * stack, const CSpell * spell)
|
||||
{
|
||||
auto result = new ColorTransformAnimation(owner, stack, spell);
|
||||
result->steps.push_back(ColorFilter::genEmptyShifter());
|
||||
result->steps.push_back(ColorFilter::genGrayscaleShifter());
|
||||
result->timePoints.push_back(0.f);
|
||||
result->timePoints.push_back(1.f);
|
||||
return result;
|
||||
}
|
||||
|
||||
ColorTransformAnimation * ColorTransformAnimation::fadeInAnimation(BattleInterface & owner, const CStack * stack)
|
||||
{
|
||||
auto result = new ColorTransformAnimation(owner, stack, nullptr);
|
||||
result->steps.push_back(ColorFilter::genAlphaShifter(0.f));
|
||||
result->steps.push_back(ColorFilter::genEmptyShifter());
|
||||
result->timePoints.push_back(0.f);
|
||||
result->timePoints.push_back(1.f);
|
||||
return result;
|
||||
}
|
||||
|
||||
ColorTransformAnimation * ColorTransformAnimation::fadeOutAnimation(BattleInterface & owner, const CStack * stack)
|
||||
{
|
||||
auto result = fadeInAnimation(owner, stack);
|
||||
std::swap(result->steps[0], result->steps[1]);
|
||||
return result;
|
||||
}
|
||||
|
||||
RangedAttackAnimation::RangedAttackAnimation(BattleInterface & owner_, const CStack * attacker, BattleHex dest_, const CStack * defender)
|
||||
: AttackAnimation(owner_, attacker, dest_, defender),
|
||||
@ -735,11 +804,8 @@ void RangedAttackAnimation::nextFrame()
|
||||
// animation should be paused if there is an active projectile
|
||||
if (projectileEmitted)
|
||||
{
|
||||
if (owner.projectilesController->hasActiveProjectile(attackingStack))
|
||||
stackAnimation(attackingStack)->pause();
|
||||
else
|
||||
if (!owner.projectilesController->hasActiveProjectile(attackingStack, false))
|
||||
{
|
||||
stackAnimation(attackingStack)->play();
|
||||
if(owner.getAnimationCondition(EAnimationEvents::HIT) == false)
|
||||
owner.setAnimationCondition(EAnimationEvents::HIT, true);
|
||||
}
|
||||
@ -753,7 +819,6 @@ void RangedAttackAnimation::nextFrame()
|
||||
if ( stackAnimation(attackingStack)->getCurrentFrame() >= getAttackClimaxFrame() )
|
||||
{
|
||||
emitProjectile();
|
||||
stackAnimation(attackingStack)->pause();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -761,7 +826,7 @@ void RangedAttackAnimation::nextFrame()
|
||||
|
||||
RangedAttackAnimation::~RangedAttackAnimation()
|
||||
{
|
||||
assert(!owner.projectilesController->hasActiveProjectile(attackingStack));
|
||||
assert(!owner.projectilesController->hasActiveProjectile(attackingStack, false));
|
||||
assert(projectileEmitted);
|
||||
|
||||
// FIXME: is this possible? Animation is over but we're yet to fire projectile?
|
||||
@ -822,7 +887,7 @@ void CatapultAnimation::nextFrame()
|
||||
if ( !projectileEmitted)
|
||||
return;
|
||||
|
||||
if (owner.projectilesController->hasActiveProjectile(attackingStack))
|
||||
if (owner.projectilesController->hasActiveProjectile(attackingStack, false))
|
||||
return;
|
||||
|
||||
explosionEmitted = true;
|
||||
@ -1171,7 +1236,7 @@ void HeroCastAnimation::nextFrame()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!owner.projectilesController->hasActiveProjectile(nullptr))
|
||||
if (!owner.projectilesController->hasActiveProjectile(nullptr, false))
|
||||
{
|
||||
emitAnimationEvent();
|
||||
//TODO: check H3 - it is possible that hero animation should be paused until hit effect is over, not just projectile
|
||||
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../lib/battle/BattleHex.h"
|
||||
#include "../gui/Geometries.h"
|
||||
#include "BattleConstants.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
@ -20,6 +21,8 @@ class CSpell;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
struct SDL_Color;
|
||||
class ColorFilter;
|
||||
class BattleHero;
|
||||
class CAnimation;
|
||||
class BattleInterface;
|
||||
@ -213,17 +216,25 @@ public:
|
||||
ResurrectionAnimation(BattleInterface & owner, const CStack * _stack);
|
||||
};
|
||||
|
||||
/// Performs fade-in or fade-out animation on stack
|
||||
class FadingAnimation : public BattleStackAnimation
|
||||
class ColorTransformAnimation : public BattleStackAnimation
|
||||
{
|
||||
float progress;
|
||||
uint8_t from;
|
||||
uint8_t dest;
|
||||
public:
|
||||
std::vector<ColorFilter> steps;
|
||||
std::vector<float> timePoints;
|
||||
const CSpell * spell;
|
||||
|
||||
float totalProgress;
|
||||
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
|
||||
FadingAnimation(BattleInterface & owner, const CStack * _stack, uint8_t alphaFrom, uint8_t alphaDest);
|
||||
ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const CSpell * spell);
|
||||
public:
|
||||
|
||||
static ColorTransformAnimation * petrifyAnimation (BattleInterface & owner, const CStack * _stack, const CSpell * spell);
|
||||
static ColorTransformAnimation * cloneAnimation (BattleInterface & owner, const CStack * _stack, const CSpell * spell);
|
||||
static ColorTransformAnimation * bloodlustAnimation(BattleInterface & owner, const CStack * _stack, const CSpell * spell);
|
||||
static ColorTransformAnimation * fadeInAnimation (BattleInterface & owner, const CStack * _stack);
|
||||
static ColorTransformAnimation * fadeOutAnimation (BattleInterface & owner, const CStack * _stack);
|
||||
};
|
||||
|
||||
class RangedAttackAnimation : public AttackAnimation
|
||||
|
@ -457,6 +457,7 @@ void BattleInterface::gateStateChanged(const EGateState state)
|
||||
void BattleInterface::battleFinished(const BattleResult& br)
|
||||
{
|
||||
bresult = &br;
|
||||
assert(getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
stacksController->setActiveStack(nullptr);
|
||||
displayBattleFinished();
|
||||
@ -537,7 +538,12 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
|
||||
if(stack)
|
||||
{
|
||||
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
|
||||
displaySpellEffect(spellID, stack->getPosition());
|
||||
if (spellID == SpellID::BLOODLUST)
|
||||
stacksController->addNewAnim( ColorTransformAnimation::bloodlustAnimation(*this, stack, spell));
|
||||
else if (spellID == SpellID::STONE_GAZE)
|
||||
stacksController->addNewAnim( ColorTransformAnimation::petrifyAnimation(*this, stack, spell));
|
||||
else
|
||||
displaySpellEffect(spellID, stack->getPosition());
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -794,6 +800,7 @@ void BattleInterface::tacticNextStack(const CStack * current)
|
||||
current = stacksController->getActiveStack();
|
||||
|
||||
//no switching stacks when the current one is moving
|
||||
assert(getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
|
||||
TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE);
|
||||
|
@ -56,8 +56,8 @@ void ProjectileMissile::show(Canvas & canvas)
|
||||
float progress = float(step) / steps;
|
||||
|
||||
Point pos {
|
||||
CSDL_Ext::lerp(from.x, dest.x, progress) - image->width() / 2,
|
||||
CSDL_Ext::lerp(from.y, dest.y, progress) - image->height() / 2,
|
||||
vstd::lerp(from.x, dest.x, progress) - image->width() / 2,
|
||||
vstd::lerp(from.y, dest.y, progress) - image->height() / 2,
|
||||
};
|
||||
|
||||
canvas.draw(image, pos);
|
||||
@ -84,7 +84,7 @@ void ProjectileCatapult::show(Canvas & canvas)
|
||||
{
|
||||
float progress = float(step) / steps;
|
||||
|
||||
int posX = CSDL_Ext::lerp(from.x, dest.x, progress);
|
||||
int posX = vstd::lerp(from.x, dest.x, progress);
|
||||
int posY = calculateCatapultParabolaY(from, dest, posX);
|
||||
Point pos(posX, posY);
|
||||
|
||||
@ -100,8 +100,8 @@ void ProjectileRay::show(Canvas & canvas)
|
||||
float progress = float(step) / steps;
|
||||
|
||||
Point curr {
|
||||
CSDL_Ext::lerp(from.x, dest.x, progress),
|
||||
CSDL_Ext::lerp(from.y, dest.y, progress),
|
||||
vstd::lerp(from.x, dest.x, progress),
|
||||
vstd::lerp(from.y, dest.y, progress),
|
||||
};
|
||||
|
||||
Point length = curr - from;
|
||||
@ -235,13 +235,13 @@ void BattleProjectileController::showProjectiles(Canvas & canvas)
|
||||
});
|
||||
}
|
||||
|
||||
bool BattleProjectileController::hasActiveProjectile(const CStack * stack) const
|
||||
bool BattleProjectileController::hasActiveProjectile(const CStack * stack, bool emittedOnly) const
|
||||
{
|
||||
int stackID = stack ? stack->ID : -1;
|
||||
|
||||
for(auto const & instance : projectiles)
|
||||
{
|
||||
if(instance->shooterID == stackID)
|
||||
if(instance->shooterID == stackID && (instance->playing || !emittedOnly))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ public:
|
||||
void showProjectiles(Canvas & canvas);
|
||||
|
||||
/// returns true if stack has projectile that is yet to hit target
|
||||
bool hasActiveProjectile(const CStack * stack) const;
|
||||
bool hasActiveProjectile(const CStack * stack, bool emittedOnly) const;
|
||||
|
||||
/// starts rendering previously created projectile
|
||||
void emitStackProjectile(const CStack * stack);
|
||||
|
@ -327,9 +327,7 @@ bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const
|
||||
|
||||
void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
|
||||
{
|
||||
//FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
|
||||
if (ca.attacker != -1)
|
||||
{
|
||||
|
@ -76,15 +76,21 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
|
||||
amountNegative = IImage::createFromFile("CMNUMWIN.BMP");
|
||||
amountEffNeutral = IImage::createFromFile("CMNUMWIN.BMP");
|
||||
|
||||
static const ColorShifterRangeExcept shifterNormal ({0,0,0,0}, {150, 50, 255, 255}, {255, 231, 132, 255});
|
||||
static const ColorShifterRangeExcept shifterPositive({0,0,0,0}, { 50, 255, 50, 255}, {255, 231, 132, 255});
|
||||
static const ColorShifterRangeExcept shifterNegative({0,0,0,0}, {255, 50, 50, 255}, {255, 231, 132, 255});
|
||||
static const ColorShifterRangeExcept shifterNeutral ({0,0,0,0}, {255, 255, 50, 255}, {255, 231, 132, 255});
|
||||
static const auto shifterNormal = ColorFilter::genRangeShifter( 0,0,0, 0.6, 0.2, 1.0 );
|
||||
static const auto shifterPositive = ColorFilter::genRangeShifter( 0,0,0, 0.2, 1.0, 0.2 );
|
||||
static const auto shifterNegative = ColorFilter::genRangeShifter( 0,0,0, 1.0, 0.2, 0.2 );
|
||||
static const auto shifterNeutral = ColorFilter::genRangeShifter( 0,0,0, 1.0, 1.0, 0.2 );
|
||||
|
||||
amountNormal->adjustPalette(&shifterNormal);
|
||||
amountPositive->adjustPalette(&shifterPositive);
|
||||
amountNegative->adjustPalette(&shifterNegative);
|
||||
amountEffNeutral->adjustPalette(&shifterNeutral);
|
||||
amountNormal->adjustPalette(shifterNormal);
|
||||
amountPositive->adjustPalette(shifterPositive);
|
||||
amountNegative->adjustPalette(shifterNegative);
|
||||
amountEffNeutral->adjustPalette(shifterNeutral);
|
||||
|
||||
//Restore border color {255, 231, 132, 255} to its original state
|
||||
amountNormal->resetPalette(26);
|
||||
amountPositive->resetPalette(26);
|
||||
amountNegative->resetPalette(26);
|
||||
amountEffNeutral->resetPalette(26);
|
||||
|
||||
std::vector<const CStack*> stacks = owner.curInt->cb->battleGetAllStacks(true);
|
||||
for(const CStack * s : stacks)
|
||||
@ -98,7 +104,7 @@ BattleHex BattleStacksController::getStackCurrentPosition(const CStack * stack)
|
||||
if ( !stackAnimation.at(stack->ID)->isMoving())
|
||||
return stack->getPosition();
|
||||
|
||||
if (stack->hasBonusOfType(Bonus::FLYING))
|
||||
if (stack->hasBonusOfType(Bonus::FLYING) && stackAnimation.at(stack->ID)->getType() == ECreatureAnimType::MOVING )
|
||||
return BattleHex::HEX_AFTER_ALL;
|
||||
|
||||
for (auto & anim : currentAnimations)
|
||||
@ -147,9 +153,7 @@ void BattleStacksController::collectRenderableObjects(BattleRenderer & renderer)
|
||||
|
||||
void BattleStacksController::stackReset(const CStack * stack)
|
||||
{
|
||||
//FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end
|
||||
//assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
//owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
|
||||
//reset orientation?
|
||||
//stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER;
|
||||
@ -171,13 +175,6 @@ void BattleStacksController::stackReset(const CStack * stack)
|
||||
addNewAnim(new ResurrectionAnimation(owner, stack));
|
||||
});
|
||||
}
|
||||
|
||||
//static const ColorShifterMultiplyAndAdd shifterClone ({255, 255, 0, 255}, {0, 0, 255, 0});
|
||||
//if (stack->isClone())
|
||||
//{
|
||||
// animation->shiftColor(&shifterClone);
|
||||
//}
|
||||
//owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
}
|
||||
|
||||
void BattleStacksController::stackAdded(const CStack * stack, bool instant)
|
||||
@ -213,12 +210,15 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
|
||||
|
||||
if (!instant)
|
||||
{
|
||||
ColorShifterRange shifterFade ({0, 0, 0, 0}, {255, 255, 255, 0});
|
||||
stackAnimation[stack->ID]->shiftColor(&shifterFade);
|
||||
// immediately make stack transparent, giving correct shifter time to start
|
||||
auto shifterFade = ColorFilter::genAlphaShifter(0);
|
||||
setStackColorFilter(shifterFade, stack, nullptr, true);
|
||||
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]()
|
||||
{
|
||||
addNewAnim(new FadingAnimation(owner, stack, 0, 255));
|
||||
addNewAnim(ColorTransformAnimation::fadeInAnimation(owner, stack));
|
||||
if (stack->isClone())
|
||||
addNewAnim(ColorTransformAnimation::cloneAnimation(owner, stack, SpellID(SpellID::CLONE).toSpell()));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -353,7 +353,23 @@ void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack *
|
||||
|
||||
void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)
|
||||
{
|
||||
stackAnimation[stack->ID]->nextFrame(canvas, facingRight(stack)); // do actual blit
|
||||
ColorFilter fullFilter = ColorFilter::genEmptyShifter();
|
||||
for (auto const & filter : stackFilterEffects)
|
||||
{
|
||||
if (filter.target == stack)
|
||||
fullFilter = ColorFilter::genCombined(fullFilter, filter.effect);
|
||||
}
|
||||
|
||||
bool stackHasProjectile = owner.projectilesController->hasActiveProjectile(stack, true);
|
||||
//bool stackPetrified = stack->hasBonus(Selector::source(Bonus::SPELL_EFFECT, SpellID::STONE_GAZE));
|
||||
//bool stackFrozen = stackHasProjectile || stackPetrified;
|
||||
|
||||
if (stackHasProjectile)
|
||||
stackAnimation[stack->ID]->pause();
|
||||
else
|
||||
stackAnimation[stack->ID]->play();
|
||||
|
||||
stackAnimation[stack->ID]->nextFrame(canvas, fullFilter, facingRight(stack)); // do actual blit
|
||||
stackAnimation[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
|
||||
}
|
||||
|
||||
@ -387,14 +403,6 @@ void BattleStacksController::addNewAnim(BattleAnimation *anim)
|
||||
owner.setAnimationCondition(EAnimationEvents::ACTION, true);
|
||||
}
|
||||
|
||||
void BattleStacksController::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities
|
||||
{
|
||||
stackToActivate = stack;
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
if (stackToActivate) //during waiting stack may have gotten activated through show
|
||||
owner.activateStack();
|
||||
}
|
||||
|
||||
void BattleStacksController::stackRemoved(uint32_t stackID)
|
||||
{
|
||||
if (getActiveStack() && getActiveStack()->ID == stackID)
|
||||
@ -410,6 +418,11 @@ void BattleStacksController::stackRemoved(uint32_t stackID)
|
||||
|
||||
void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
|
||||
// remove any potentially erased petrification effect
|
||||
removeExpiredColorFilters();
|
||||
});
|
||||
|
||||
for(auto & attackedInfo : attackedInfos)
|
||||
{
|
||||
if (!attackedInfo.attacker)
|
||||
@ -466,9 +479,10 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
|
||||
});
|
||||
}
|
||||
|
||||
if (attackedInfo.cloneKilled)
|
||||
if (attackedInfo.killed && attackedInfo.defender->summoned)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
|
||||
addNewAnim(ColorTransformAnimation::fadeOutAnimation(owner, attackedInfo.defender));
|
||||
stackRemoved(attackedInfo.defender->ID);
|
||||
});
|
||||
}
|
||||
@ -479,8 +493,6 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
|
||||
void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
|
||||
{
|
||||
assert(destHex.size() > 0);
|
||||
|
||||
//FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
|
||||
if(shouldRotate(stack, stack->getPosition(), destHex[0]))
|
||||
@ -498,7 +510,6 @@ void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleH
|
||||
|
||||
void BattleStacksController::stackAttacking( const StackAttackInfo & info )
|
||||
{
|
||||
//FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
|
||||
bool needsReverse =
|
||||
@ -566,7 +577,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
|
||||
}
|
||||
});
|
||||
|
||||
if (info.spellEffect)
|
||||
if (info.spellEffect != SpellID::NONE)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]()
|
||||
{
|
||||
@ -626,9 +637,7 @@ bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex
|
||||
|
||||
void BattleStacksController::endAction(const BattleAction* action)
|
||||
{
|
||||
//FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
|
||||
//check if we should reverse stacks
|
||||
TStacks stacks = owner.curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY);
|
||||
@ -644,7 +653,7 @@ void BattleStacksController::endAction(const BattleAction* action)
|
||||
}
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
|
||||
//FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end
|
||||
//Ensure that all animation flags were reset
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::OPENING) == false);
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::MOVEMENT) == false);
|
||||
@ -653,14 +662,23 @@ void BattleStacksController::endAction(const BattleAction* action)
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::PROJECTILES) == false);
|
||||
|
||||
owner.controlPanel->blockUI(activeStack == nullptr);
|
||||
removeExpiredColorFilters();
|
||||
}
|
||||
|
||||
void BattleStacksController::startAction(const BattleAction* action)
|
||||
{
|
||||
setHoveredStack(nullptr);
|
||||
removeExpiredColorFilters();
|
||||
}
|
||||
|
||||
void BattleStacksController::activateStack()
|
||||
void BattleStacksController::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities
|
||||
{
|
||||
stackToActivate = stack;
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
owner.activateStack();
|
||||
}
|
||||
|
||||
void BattleStacksController::activateStack() //TODO: check it all before game state is changed due to abilities
|
||||
{
|
||||
if ( !currentAnimations.empty())
|
||||
return;
|
||||
@ -765,5 +783,32 @@ Point BattleStacksController::getStackPositionAtHex(BattleHex hexNum, const CSta
|
||||
}
|
||||
//returning
|
||||
return ret + owner.pos.topLeft();
|
||||
|
||||
}
|
||||
|
||||
void BattleStacksController::setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell * source, bool persistent)
|
||||
{
|
||||
for (auto & filter : stackFilterEffects)
|
||||
{
|
||||
if (filter.target == target && filter.source == source)
|
||||
{
|
||||
filter.effect = effect;
|
||||
filter.persistent = persistent;
|
||||
return;
|
||||
}
|
||||
}
|
||||
stackFilterEffects.push_back({ effect, target, source, persistent });
|
||||
}
|
||||
|
||||
void BattleStacksController::removeExpiredColorFilters()
|
||||
{
|
||||
vstd::erase_if(stackFilterEffects, [&](const BattleStackFilterEffect & filter)
|
||||
{
|
||||
if (filter.persistent)
|
||||
return false;
|
||||
if (filter.effect == ColorFilter::genEmptyShifter())
|
||||
return false;
|
||||
if (filter.target->hasBonus(Selector::source(Bonus::SPELL_EFFECT, filter.source->id)))
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
@ -10,12 +10,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "../gui/Geometries.h"
|
||||
#include "../gui/ColorFilter.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct BattleHex;
|
||||
class BattleAction;
|
||||
class CStack;
|
||||
class CSpell;
|
||||
class SpellID;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
@ -23,6 +25,7 @@ VCMI_LIB_NAMESPACE_END
|
||||
struct StackAttackedInfo;
|
||||
struct StackAttackInfo;
|
||||
|
||||
class ColorFilter;
|
||||
class Canvas;
|
||||
class BattleInterface;
|
||||
class BattleAnimation;
|
||||
@ -31,6 +34,14 @@ class BattleAnimation;
|
||||
class BattleRenderer;
|
||||
class IImage;
|
||||
|
||||
struct BattleStackFilterEffect
|
||||
{
|
||||
ColorFilter effect;
|
||||
const CStack * target;
|
||||
const CSpell * source;
|
||||
bool persistent;
|
||||
};
|
||||
|
||||
/// Class responsible for handling stacks in battle
|
||||
/// Handles ordering of stacks animation
|
||||
/// As well as rendering of stacks, their amount boxes
|
||||
@ -47,13 +58,16 @@ class BattleStacksController
|
||||
/// currently displayed animations <anim, initialized>
|
||||
std::vector<BattleAnimation *> currentAnimations;
|
||||
|
||||
/// currently active color effects on stacks, in order of their addition (which corresponds to their apply order)
|
||||
std::vector<BattleStackFilterEffect> stackFilterEffects;
|
||||
|
||||
/// animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
|
||||
std::map<int32_t, std::shared_ptr<CreatureAnimation>> stackAnimation;
|
||||
|
||||
/// <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
|
||||
std::map<int, bool> stackFacingRight;
|
||||
|
||||
/// number of active stack; nullptr - no one
|
||||
/// currently active stack; nullptr - no one
|
||||
const CStack *activeStack;
|
||||
|
||||
/// stack below mouse pointer, used for border animation
|
||||
@ -79,6 +93,7 @@ class BattleStacksController
|
||||
std::shared_ptr<IImage> getStackAmountBox(const CStack * stack);
|
||||
|
||||
void executeAttackAnimations();
|
||||
void removeExpiredColorFilters();
|
||||
public:
|
||||
BattleStacksController(BattleInterface & owner);
|
||||
|
||||
@ -110,6 +125,10 @@ public:
|
||||
|
||||
void collectRenderableObjects(BattleRenderer & renderer);
|
||||
|
||||
/// Adds new color filter effect targeting stack
|
||||
/// Effect will last as long as stack is affected by specified spell (unless effect is persistent)
|
||||
/// If effect from same (target, source) already exists, it will be updated
|
||||
void setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell *source, bool persistent);
|
||||
void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims
|
||||
void updateBattleAnimations();
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
|
||||
#include "../gui/Canvas.h"
|
||||
#include "../gui/ColorFilter.h"
|
||||
|
||||
static const SDL_Color creatureBlueBorder = { 0, 255, 255, 255 };
|
||||
static const SDL_Color creatureGoldBorder = { 255, 255, 0, 255 };
|
||||
@ -158,19 +159,6 @@ void CreatureAnimation::setType(ECreatureAnimType::Type type)
|
||||
play();
|
||||
}
|
||||
|
||||
void CreatureAnimation::shiftColor(const ColorShifter* shifter)
|
||||
{
|
||||
SDL_Color shadowTest = shifter->shiftColor(genShadow(128));
|
||||
|
||||
shadowAlpha = shadowTest.a;
|
||||
|
||||
if(forward)
|
||||
forward->shiftColor(shifter);
|
||||
|
||||
if(reverse)
|
||||
reverse->shiftColor(shifter);
|
||||
}
|
||||
|
||||
CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController controller)
|
||||
: name(name_),
|
||||
speed(0.1f),
|
||||
@ -327,8 +315,11 @@ void CreatureAnimation::genSpecialPalette(IImage::SpecialPalette & target)
|
||||
target[6] = addColors(genShadow(shadowAlpha / 2), genBorderColor(getBorderStrength(elapsedTime), border));
|
||||
}
|
||||
|
||||
void CreatureAnimation::nextFrame(Canvas & canvas, bool facingRight)
|
||||
void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter, bool facingRight)
|
||||
{
|
||||
SDL_Color shadowTest = shifter.shiftColor(genShadow(128));
|
||||
shadowAlpha = shadowTest.a;
|
||||
|
||||
size_t frame = static_cast<size_t>(floor(currentFrame));
|
||||
|
||||
std::shared_ptr<IImage> image;
|
||||
@ -344,6 +335,7 @@ void CreatureAnimation::nextFrame(Canvas & canvas, bool facingRight)
|
||||
genSpecialPalette(SpecialPalette);
|
||||
|
||||
image->setSpecialPallete(SpecialPalette);
|
||||
image->adjustPalette(shifter);
|
||||
|
||||
canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
|
||||
|
||||
|
@ -118,16 +118,13 @@ public:
|
||||
/// returns currently rendered type of animation
|
||||
ECreatureAnimType::Type getType() const;
|
||||
|
||||
void nextFrame(Canvas & canvas, bool facingRight);
|
||||
void nextFrame(Canvas & canvas, const ColorFilter & shifter, bool facingRight);
|
||||
|
||||
/// should be called every frame, return true when animation was reset to beginning
|
||||
bool incrementFrame(float timePassed);
|
||||
|
||||
void setBorderColor(SDL_Color palette);
|
||||
|
||||
/// apply color tint effect
|
||||
void shiftColor(const ColorShifter * shifter);
|
||||
|
||||
/// Gets the current frame ID within current group.
|
||||
float getCurrentFrame() const;
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include "SDL_Extensions.h"
|
||||
#include "SDL_Pixels.h"
|
||||
#include "ColorFilter.h"
|
||||
|
||||
#include "../CBitmapHandler.h"
|
||||
#include "../Graphics.h"
|
||||
@ -105,7 +106,8 @@ public:
|
||||
void verticalFlip() override;
|
||||
|
||||
void shiftPalette(int from, int howMany) override;
|
||||
void adjustPalette(const ColorShifter * shifter) override;
|
||||
void adjustPalette(const ColorFilter & shifter) override;
|
||||
void resetPalette(int colorID) override;
|
||||
void resetPalette() override;
|
||||
|
||||
void setSpecialPallete(const SpecialPalette & SpecialPalette) override;
|
||||
@ -789,7 +791,7 @@ void SDLImage::shiftPalette(int from, int howMany)
|
||||
}
|
||||
}
|
||||
|
||||
void SDLImage::adjustPalette(const ColorShifter * shifter)
|
||||
void SDLImage::adjustPalette(const ColorFilter & shifter)
|
||||
{
|
||||
if(originalPalette == nullptr)
|
||||
return;
|
||||
@ -799,7 +801,7 @@ void SDLImage::adjustPalette(const ColorShifter * shifter)
|
||||
// Note: here we skip the first 8 colors in the palette that predefined in H3Palette
|
||||
for(int i = 8; i < palette->ncolors; i++)
|
||||
{
|
||||
palette->colors[i] = shifter->shiftColor(originalPalette->colors[i]);
|
||||
palette->colors[i] = shifter.shiftColor(originalPalette->colors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -812,6 +814,15 @@ void SDLImage::resetPalette()
|
||||
SDL_SetPaletteColors(surf->format->palette, originalPalette->colors, 0, originalPalette->ncolors);
|
||||
}
|
||||
|
||||
void SDLImage::resetPalette( int colorID )
|
||||
{
|
||||
if(originalPalette == nullptr)
|
||||
return;
|
||||
|
||||
// Always keept the original palette not changed, copy a new palette to assign to surface
|
||||
SDL_SetPaletteColors(surf->format->palette, originalPalette->colors + colorID, colorID, 1);
|
||||
}
|
||||
|
||||
void SDLImage::setSpecialPallete(const IImage::SpecialPalette & SpecialPalette)
|
||||
{
|
||||
if(surf->format->palette)
|
||||
@ -1079,7 +1090,7 @@ void CAnimation::duplicateImage(const size_t sourceGroup, const size_t sourceFra
|
||||
load(index, targetGroup);
|
||||
}
|
||||
|
||||
void CAnimation::shiftColor(const ColorShifter * shifter)
|
||||
void CAnimation::shiftColor(const ColorFilter & shifter)
|
||||
{
|
||||
for(auto groupIter = images.begin(); groupIter != images.end(); groupIter++)
|
||||
{
|
||||
|
@ -29,7 +29,7 @@ VCMI_LIB_NAMESPACE_END
|
||||
|
||||
struct SDL_Surface;
|
||||
class CDefFile;
|
||||
class ColorShifter;
|
||||
class ColorFilter;
|
||||
|
||||
/*
|
||||
* Base class for images, can be used for non-animation pictures as well
|
||||
@ -62,7 +62,8 @@ public:
|
||||
|
||||
//only indexed bitmaps, 16 colors maximum
|
||||
virtual void shiftPalette(int from, int howMany) = 0;
|
||||
virtual void adjustPalette(const ColorShifter * shifter) = 0;
|
||||
virtual void adjustPalette(const ColorFilter & shifter) = 0;
|
||||
virtual void resetPalette(int colorID) = 0;
|
||||
virtual void resetPalette() = 0;
|
||||
|
||||
//only indexed bitmaps with 7 special colors
|
||||
@ -122,7 +123,7 @@ public:
|
||||
void duplicateImage(const size_t sourceGroup, const size_t sourceFrame, const size_t targetGroup);
|
||||
|
||||
// adjust the color of the animation, used in battle spell effects, e.g. Cloned objects
|
||||
void shiftColor(const ColorShifter * shifter);
|
||||
void shiftColor(const ColorFilter & shifter);
|
||||
|
||||
//add custom surface to the selected position.
|
||||
void setCustom(std::string filename, size_t frame, size_t group=0);
|
||||
|
@ -267,7 +267,7 @@ void CIntObject::addChild(CIntObject * child, bool adjustPosition)
|
||||
children.push_back(child);
|
||||
child->parent_m = this;
|
||||
if(adjustPosition)
|
||||
child->pos += pos;
|
||||
child->pos += pos.topLeft();
|
||||
|
||||
if (!active && child->active)
|
||||
child->deactivate();
|
||||
@ -289,7 +289,7 @@ void CIntObject::removeChild(CIntObject * child, bool adjustPosition)
|
||||
children -= child;
|
||||
child->parent_m = nullptr;
|
||||
if(adjustPosition)
|
||||
child->pos -= pos;
|
||||
child->pos -= pos.topLeft();
|
||||
}
|
||||
|
||||
void CIntObject::redraw()
|
||||
|
115
client/gui/ColorFilter.cpp
Normal file
115
client/gui/ColorFilter.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Canvas.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 "ColorFilter.h"
|
||||
|
||||
#include <SDL2/SDL_pixels.h>
|
||||
|
||||
SDL_Color ColorFilter::shiftColor(const SDL_Color & in) const
|
||||
{
|
||||
SDL_Color out;
|
||||
out.r = in.r * r.r + in.g * r.g + in.b * r.b + 255 * r.a;
|
||||
out.g = in.r * g.r + in.g * g.g + in.b * g.b + 255 * g.a;
|
||||
out.b = in.r * b.r + in.g * b.g + in.b * b.b + 255 * b.a;
|
||||
out.a = in.a * a;
|
||||
return out;
|
||||
}
|
||||
|
||||
bool ColorFilter::operator == (const ColorFilter & other) const
|
||||
{
|
||||
return
|
||||
r.r == other.r.r && r.g && other.r.g && r.b == other.r.b && r.a == other.r.a &&
|
||||
g.r == other.g.r && g.g && other.g.g && g.b == other.g.b && g.a == other.g.a &&
|
||||
b.r == other.b.r && b.g && other.b.g && b.b == other.b.b && b.a == other.b.a &&
|
||||
a == other.a;
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genEmptyShifter( )
|
||||
{
|
||||
return genAlphaShifter( 1.f);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genAlphaShifter( float alpha )
|
||||
{
|
||||
return genMuxerShifter(
|
||||
{ 1.f, 0.f, 0.f, 0.f },
|
||||
{ 0.f, 1.f, 0.f, 0.f },
|
||||
{ 0.f, 0.f, 1.f, 0.f },
|
||||
alpha);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genGrayscaleShifter( )
|
||||
{
|
||||
ChannelMuxer gray({0.299f, 0.587f, 0.114f, 0.f});
|
||||
|
||||
return genMuxerShifter(gray, gray, gray, 1.f);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genRangeShifter( float minR, float minG, float minB, float maxR, float maxG, float maxB )
|
||||
{
|
||||
return genMuxerShifter(
|
||||
{ maxR - minR, 0.f, 0.f, minR },
|
||||
{ 0.f, maxG - minG, 0.f, minG },
|
||||
{ 0.f, 0.f, maxB - minB, minB },
|
||||
1.f);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a )
|
||||
{
|
||||
return ColorFilter(r, g, b, a);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genInterpolated(const ColorFilter & left, const ColorFilter & right, float power)
|
||||
{
|
||||
auto lerpMuxer = [=]( const ChannelMuxer & left, const ChannelMuxer & right ) -> ChannelMuxer
|
||||
{
|
||||
return {
|
||||
vstd::lerp(left.r, right.r, power),
|
||||
vstd::lerp(left.g, right.g, power),
|
||||
vstd::lerp(left.b, right.b, power),
|
||||
vstd::lerp(left.a, right.a, power)
|
||||
};
|
||||
};
|
||||
|
||||
return genMuxerShifter(
|
||||
lerpMuxer(left.r, right.r),
|
||||
lerpMuxer(left.g, right.g),
|
||||
lerpMuxer(left.b, right.b),
|
||||
vstd::lerp(left.a, right.a, power)
|
||||
);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genCombined(const ColorFilter & left, const ColorFilter & right)
|
||||
{
|
||||
// matrix multiplication
|
||||
ChannelMuxer r{
|
||||
left.r.r * right.r.r + left.g.r * right.r.g + left.b.r * right.r.b,
|
||||
left.r.g * right.r.r + left.g.g * right.r.g + left.b.g * right.r.b,
|
||||
left.r.b * right.r.r + left.g.b * right.r.g + left.b.b * right.r.b,
|
||||
left.r.a * right.r.r + left.g.a * right.r.g + left.b.a * right.r.b + 1.f * right.r.a,
|
||||
};
|
||||
|
||||
ChannelMuxer g{
|
||||
left.r.r * right.g.r + left.g.r * right.g.g + left.b.r * right.g.b,
|
||||
left.r.g * right.g.r + left.g.g * right.g.g + left.b.g * right.g.b,
|
||||
left.r.b * right.g.r + left.g.b * right.g.g + left.b.b * right.g.b,
|
||||
left.r.a * right.g.r + left.g.a * right.g.g + left.b.a * right.g.b + 1.f * right.g.a,
|
||||
};
|
||||
|
||||
ChannelMuxer b{
|
||||
left.r.r * right.b.r + left.g.r * right.b.g + left.b.r * right.b.b,
|
||||
left.r.g * right.b.r + left.g.g * right.b.g + left.b.g * right.b.b,
|
||||
left.r.b * right.b.r + left.g.b * right.b.g + left.b.b * right.b.b,
|
||||
left.r.a * right.b.r + left.g.a * right.b.g + left.b.a * right.b.b + 1.f * right.b.a,
|
||||
};
|
||||
|
||||
float a = left.a * right.a;
|
||||
return genMuxerShifter(r,g,b,a);
|
||||
}
|
55
client/gui/ColorFilter.h
Normal file
55
client/gui/ColorFilter.h
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* ColorFilter.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
|
||||
*
|
||||
*/
|
||||
|
||||
struct SDL_Color;
|
||||
|
||||
#pragma once
|
||||
|
||||
/// Base class for applying palette transformation on images
|
||||
class ColorFilter
|
||||
{
|
||||
struct ChannelMuxer {
|
||||
float r, g, b, a;
|
||||
};
|
||||
|
||||
ChannelMuxer r;
|
||||
ChannelMuxer g;
|
||||
ChannelMuxer b;
|
||||
float a;
|
||||
|
||||
ColorFilter(ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a):
|
||||
r(r), g(g), b(b), a(a)
|
||||
{}
|
||||
public:
|
||||
SDL_Color shiftColor(const SDL_Color & in) const;
|
||||
|
||||
bool operator == (const ColorFilter & other) const;
|
||||
|
||||
/// Generates empty object that has no effect on image
|
||||
static ColorFilter genEmptyShifter();
|
||||
|
||||
/// Generates object that changes alpha (transparency) of the image
|
||||
static ColorFilter genAlphaShifter( float alpha );
|
||||
|
||||
/// Generates object that applies grayscale effect to image
|
||||
static ColorFilter genGrayscaleShifter( );
|
||||
|
||||
/// Generates object that transforms each channel independently
|
||||
static ColorFilter genRangeShifter( float minR, float minG, float minB, float maxR, float maxG, float maxB );
|
||||
|
||||
/// Generates object that performs arbitrary mixing between any channels
|
||||
static ColorFilter genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a );
|
||||
|
||||
/// Combines 2 mixers into a single object
|
||||
static ColorFilter genCombined(const ColorFilter & left, const ColorFilter & right);
|
||||
|
||||
/// Scales down strength of a shifter to a specified factor
|
||||
static ColorFilter genInterpolated(const ColorFilter & left, const ColorFilter & right, float power);
|
||||
};
|
@ -11,6 +11,11 @@
|
||||
#include "Geometries.h"
|
||||
#include "../CMT.h"
|
||||
#include <SDL_events.h>
|
||||
#include "../../lib/int3.h"
|
||||
|
||||
Point::Point(const int3 &a)
|
||||
:x(a.x),y(a.y)
|
||||
{}
|
||||
|
||||
Point::Point(const SDL_MouseMotionEvent &a)
|
||||
:x(a.x),y(a.y)
|
||||
@ -21,7 +26,7 @@ Rect Rect::createCentered( int w, int h )
|
||||
return Rect(screen->w/2 - w/2, screen->h/2 - h/2, w, h);
|
||||
}
|
||||
|
||||
Rect Rect::around(const Rect &r, int width) /*creates rect around another */
|
||||
Rect Rect::around(const Rect &r, int width)
|
||||
{
|
||||
return Rect(r.x - width, r.y - width, r.w + width * 2, r.h + width * 2);
|
||||
}
|
||||
@ -30,3 +35,4 @@ Rect Rect::centerIn(const Rect &r)
|
||||
{
|
||||
return Rect(r.x + (r.w - w) / 2, r.y + (r.h - h) / 2, w, h);
|
||||
}
|
||||
|
||||
|
@ -9,13 +9,16 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <SDL_video.h>
|
||||
#include "../../lib/int3.h"
|
||||
#include <SDL2/SDL_rect.h>
|
||||
|
||||
enum class ETextAlignment {TOPLEFT, CENTER, BOTTOMRIGHT};
|
||||
|
||||
struct SDL_MouseMotionEvent;
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
class int3;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
// A point with x/y coordinate, used mostly for graphic rendering
|
||||
struct Point
|
||||
{
|
||||
@ -26,13 +29,14 @@ struct Point
|
||||
{
|
||||
x = y = 0;
|
||||
};
|
||||
|
||||
Point(int X, int Y)
|
||||
:x(X),y(Y)
|
||||
{};
|
||||
Point(const int3 &a)
|
||||
:x(a.x),y(a.y)
|
||||
{}
|
||||
Point(const SDL_MouseMotionEvent &a);
|
||||
|
||||
Point(const int3 &a);
|
||||
|
||||
explicit Point(const SDL_MouseMotionEvent &a);
|
||||
|
||||
template<typename T>
|
||||
Point operator+(const T &b) const
|
||||
@ -73,10 +77,7 @@ struct Point
|
||||
y -= b.y;
|
||||
return *this;
|
||||
}
|
||||
bool operator<(const Point &b) const //product order
|
||||
{
|
||||
return x < b.x && y < b.y;
|
||||
}
|
||||
|
||||
template<typename T> Point& operator=(const T &t)
|
||||
{
|
||||
x = t.x;
|
||||
@ -96,7 +97,7 @@ struct Point
|
||||
/// Rectangle class, which have a position and a size
|
||||
struct Rect : public SDL_Rect
|
||||
{
|
||||
Rect()//default c-tor
|
||||
Rect()
|
||||
{
|
||||
x = y = w = h = -1;
|
||||
}
|
||||
@ -121,42 +122,35 @@ struct Rect : public SDL_Rect
|
||||
w = r.w;
|
||||
h = r.h;
|
||||
}
|
||||
Rect(const Rect& r) : Rect(static_cast<const SDL_Rect&>(r))
|
||||
{}
|
||||
explicit Rect(const SDL_Surface * const &surf)
|
||||
{
|
||||
x = y = 0;
|
||||
w = surf->w;
|
||||
h = surf->h;
|
||||
}
|
||||
Rect(const Rect& r) = default;
|
||||
|
||||
Rect centerIn(const Rect &r);
|
||||
static Rect createCentered(int w, int h);
|
||||
static Rect around(const Rect &r, int width = 1); //creates rect around another
|
||||
static Rect around(const Rect &r, int width = 1);
|
||||
|
||||
bool isIn(int qx, int qy) const //determines if given point lies inside rect
|
||||
bool isIn(int qx, int qy) const
|
||||
{
|
||||
if (qx > x && qx<x+w && qy>y && qy<y+h)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
bool isIn(const Point & q) const //determines if given point lies inside rect
|
||||
bool isIn(const Point & q) const
|
||||
{
|
||||
return isIn(q.x,q.y);
|
||||
}
|
||||
Point topLeft() const //top left corner of this rect
|
||||
Point topLeft() const
|
||||
{
|
||||
return Point(x,y);
|
||||
}
|
||||
Point topRight() const //top right corner of this rect
|
||||
Point topRight() const
|
||||
{
|
||||
return Point(x+w,y);
|
||||
}
|
||||
Point bottomLeft() const //bottom left corner of this rect
|
||||
Point bottomLeft() const
|
||||
{
|
||||
return Point(x,y+h);
|
||||
}
|
||||
Point bottomRight() const //bottom right corner of this rect
|
||||
Point bottomRight() const
|
||||
{
|
||||
return Point(x+w,y+h);
|
||||
}
|
||||
@ -168,21 +162,19 @@ struct Rect : public SDL_Rect
|
||||
{
|
||||
return Point(w,h);
|
||||
}
|
||||
Rect operator+(const Rect &p) const //moves this rect by p's rect position
|
||||
|
||||
void moveTo(const Point & dest)
|
||||
{
|
||||
x = dest.x;
|
||||
y = dest.y;
|
||||
}
|
||||
|
||||
Rect operator+(const Point &p) const
|
||||
{
|
||||
return Rect(x+p.x,y+p.y,w,h);
|
||||
}
|
||||
Rect operator+(const Point &p) const //moves this rect by p's point position
|
||||
{
|
||||
return Rect(x+p.x,y+p.y,w,h);
|
||||
}
|
||||
Rect& operator=(const Point &p) //assignment operator
|
||||
{
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
return *this;
|
||||
}
|
||||
Rect& operator=(const Rect &p) //assignment operator
|
||||
|
||||
Rect& operator=(const Rect &p)
|
||||
{
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
@ -190,34 +182,21 @@ struct Rect : public SDL_Rect
|
||||
h = p.h;
|
||||
return *this;
|
||||
}
|
||||
Rect& operator+=(const Rect &p) //works as operator+
|
||||
|
||||
Rect& operator+=(const Point &p)
|
||||
{
|
||||
x += p.x;
|
||||
y += p.y;
|
||||
return *this;
|
||||
}
|
||||
Rect& operator+=(const Point &p) //works as operator+
|
||||
{
|
||||
x += p.x;
|
||||
y += p.y;
|
||||
return *this;
|
||||
}
|
||||
Rect& operator-=(const Rect &p) //works as operator+
|
||||
|
||||
Rect& operator-=(const Point &p)
|
||||
{
|
||||
x -= p.x;
|
||||
y -= p.y;
|
||||
return *this;
|
||||
}
|
||||
Rect& operator-=(const Point &p) //works as operator+
|
||||
{
|
||||
x -= p.x;
|
||||
y -= p.y;
|
||||
return *this;
|
||||
}
|
||||
template<typename T> Rect operator-(const T &t)
|
||||
{
|
||||
return Rect(x - t.x, y - t.y, w, h);
|
||||
}
|
||||
|
||||
Rect operator&(const Rect &p) const //rect intersection
|
||||
{
|
||||
bool intersect = true;
|
||||
|
@ -349,12 +349,12 @@ static void drawLineX(SDL_Surface * sur, int x1, int y1, int x2, int y2, const S
|
||||
for(int x = x1; x <= x2; x++)
|
||||
{
|
||||
float f = float(x - x1) / float(x2 - x1);
|
||||
int y = CSDL_Ext::lerp(y1, y2, f);
|
||||
int y = vstd::lerp(y1, y2, f);
|
||||
|
||||
uint8_t r = CSDL_Ext::lerp(color1.r, color2.r, f);
|
||||
uint8_t g = CSDL_Ext::lerp(color1.g, color2.g, f);
|
||||
uint8_t b = CSDL_Ext::lerp(color1.b, color2.b, f);
|
||||
uint8_t a = CSDL_Ext::lerp(color1.a, color2.a, f);
|
||||
uint8_t r = vstd::lerp(color1.r, color2.r, f);
|
||||
uint8_t g = vstd::lerp(color1.g, color2.g, f);
|
||||
uint8_t b = vstd::lerp(color1.b, color2.b, f);
|
||||
uint8_t a = vstd::lerp(color1.a, color2.a, f);
|
||||
|
||||
Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y);
|
||||
ColorPutter<4, 0>::PutColor(p, r,g,b,a);
|
||||
@ -366,12 +366,12 @@ static void drawLineY(SDL_Surface * sur, int x1, int y1, int x2, int y2, const S
|
||||
for(int y = y1; y <= y2; y++)
|
||||
{
|
||||
float f = float(y - y1) / float(y2 - y1);
|
||||
int x = CSDL_Ext::lerp(x1, x2, f);
|
||||
int x = vstd::lerp(x1, x2, f);
|
||||
|
||||
uint8_t r = CSDL_Ext::lerp(color1.r, color2.r, f);
|
||||
uint8_t g = CSDL_Ext::lerp(color1.g, color2.g, f);
|
||||
uint8_t b = CSDL_Ext::lerp(color1.b, color2.b, f);
|
||||
uint8_t a = CSDL_Ext::lerp(color1.a, color2.a, f);
|
||||
uint8_t r = vstd::lerp(color1.r, color2.r, f);
|
||||
uint8_t g = vstd::lerp(color1.g, color2.g, f);
|
||||
uint8_t b = vstd::lerp(color1.b, color2.b, f);
|
||||
uint8_t a = vstd::lerp(color1.a, color2.a, f);
|
||||
|
||||
Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y);
|
||||
ColorPutter<4, 0>::PutColor(p, r,g,b,a);
|
||||
@ -922,7 +922,6 @@ void CSDL_Ext::setDefaultColorKeyPresize(SDL_Surface * surface)
|
||||
}
|
||||
|
||||
|
||||
|
||||
template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<2>(int, int);
|
||||
template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<3>(int, int);
|
||||
template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<4>(int, int);
|
||||
|
@ -49,12 +49,6 @@ inline bool isShiftKeyDown()
|
||||
}
|
||||
namespace CSDL_Ext
|
||||
{
|
||||
template<typename Int>
|
||||
Int lerp(Int a, Int b, float f)
|
||||
{
|
||||
return a + std::round((b - a) * f);
|
||||
}
|
||||
|
||||
//todo: should this better be assignment operator?
|
||||
STRONG_INLINE void colorAssign(SDL_Color & dest, const SDL_Color & source)
|
||||
{
|
||||
@ -158,82 +152,6 @@ struct ColorPutter
|
||||
|
||||
typedef void (*BlitterWithRotationVal)(SDL_Surface *src,SDL_Rect srcRect, SDL_Surface * dst, SDL_Rect dstRect, ui8 rotation);
|
||||
|
||||
/// Base class for applying palette transformation on images
|
||||
class ColorShifter
|
||||
{
|
||||
public:
|
||||
~ColorShifter() = default;
|
||||
virtual SDL_Color shiftColor(SDL_Color input) const = 0;
|
||||
};
|
||||
|
||||
/// Generic class for palette transformation
|
||||
/// Applies linear transformation to move all colors into range (min, max)
|
||||
class ColorShifterRange : public ColorShifter
|
||||
{
|
||||
SDL_Color base;
|
||||
SDL_Color factor;
|
||||
|
||||
public:
|
||||
ColorShifterRange(SDL_Color min, SDL_Color max) :
|
||||
base(min)
|
||||
{
|
||||
assert(max.r >= min.r);
|
||||
assert(max.g >= min.g);
|
||||
assert(max.b >= min.b);
|
||||
assert(max.a >= min.a);
|
||||
factor.r = max.r - min.r;
|
||||
factor.g = max.g - min.g;
|
||||
factor.b = max.b - min.b;
|
||||
factor.a = max.a - min.a;
|
||||
}
|
||||
|
||||
SDL_Color shiftColor(SDL_Color input) const override
|
||||
{
|
||||
return {
|
||||
uint8_t(base.r + input.r * factor.r / 255),
|
||||
uint8_t(base.g + input.g * factor.g / 255),
|
||||
uint8_t(base.b + input.b * factor.b / 255),
|
||||
uint8_t(base.a + input.a * factor.a / 255),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Color shifter that allows to specify color to be excempt from changes
|
||||
class ColorShifterRangeExcept : public ColorShifterRange
|
||||
{
|
||||
SDL_Color ignored;
|
||||
public:
|
||||
ColorShifterRangeExcept(SDL_Color min, SDL_Color max, SDL_Color ignored) :
|
||||
ColorShifterRange(min, max),
|
||||
ignored(ignored)
|
||||
{}
|
||||
|
||||
SDL_Color shiftColor(SDL_Color input) const override
|
||||
{
|
||||
if ( input.r == ignored.r && input.g == ignored.g && input.b == ignored.b && input.a == ignored.a)
|
||||
return input;
|
||||
return ColorShifterRange::shiftColor(input);
|
||||
}
|
||||
};
|
||||
|
||||
class ColorShifterGrayscale : public ColorShifter
|
||||
{
|
||||
public:
|
||||
SDL_Color shiftColor(SDL_Color input) const override
|
||||
{
|
||||
// Apply grayscale conversion according to human eye perception values
|
||||
uint32_t gray = static_cast<uint32_t>(0.299 * input.r + 0.587 * input.g + 0.114 * input.b);
|
||||
assert(gray < 256);
|
||||
|
||||
return {
|
||||
uint8_t(gray),
|
||||
uint8_t(gray),
|
||||
uint8_t(gray),
|
||||
input.a
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
namespace CSDL_Ext
|
||||
{
|
||||
/// helper that will safely set and un-set ClipRect for SDL_Surface
|
||||
|
@ -307,7 +307,7 @@ CChatBox::CChatBox(const Rect & rect)
|
||||
: CIntObject(KEYBOARD | TEXTINPUT)
|
||||
{
|
||||
OBJ_CONSTRUCTION;
|
||||
pos += rect;
|
||||
pos += rect.topLeft();
|
||||
captureAllKeys = true;
|
||||
type |= REDRAW_PARENT;
|
||||
|
||||
@ -341,7 +341,7 @@ void CChatBox::addNewMessage(const std::string & text)
|
||||
CFlagBox::CFlagBox(const Rect & rect)
|
||||
: CIntObject(RCLICK)
|
||||
{
|
||||
pos += rect;
|
||||
pos += rect.topLeft();
|
||||
pos.w = rect.w;
|
||||
pos.h = rect.h;
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
|
@ -236,7 +236,7 @@ void CHeroList::CHeroItem::open()
|
||||
|
||||
void CHeroList::CHeroItem::showTooltip()
|
||||
{
|
||||
CRClickPopup::createAndPush(hero, GH.current->motion);
|
||||
CRClickPopup::createAndPush(hero, Point(GH.current->motion));
|
||||
}
|
||||
|
||||
std::string CHeroList::CHeroItem::getHoverText()
|
||||
@ -328,7 +328,7 @@ void CTownList::CTownItem::open()
|
||||
|
||||
void CTownList::CTownItem::showTooltip()
|
||||
{
|
||||
CRClickPopup::createAndPush(town, GH.current->motion);
|
||||
CRClickPopup::createAndPush(town, Point(GH.current->motion));
|
||||
}
|
||||
|
||||
std::string CTownList::CTownItem::getHoverText()
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "ObjectLists.h"
|
||||
#include "../../lib/FunctionList.h"
|
||||
#include "../../lib/int3.h"
|
||||
#include "Terrain.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
@ -443,7 +443,7 @@ CComponentBox::CComponentBox(std::vector<std::shared_ptr<CComponent>> _component
|
||||
components(_components)
|
||||
{
|
||||
type |= REDRAW_PARENT;
|
||||
pos = position + pos;
|
||||
pos = position + pos.topLeft();
|
||||
placeComponents(false);
|
||||
}
|
||||
|
||||
@ -452,7 +452,7 @@ CComponentBox::CComponentBox(std::vector<std::shared_ptr<CSelectableComponent>>
|
||||
onSelect(_onSelect)
|
||||
{
|
||||
type |= REDRAW_PARENT;
|
||||
pos = position + pos;
|
||||
pos = position + pos.topLeft();
|
||||
placeComponents(true);
|
||||
|
||||
assert(!components.empty());
|
||||
|
@ -167,7 +167,7 @@ void CPicture::scaleTo(Point size)
|
||||
|
||||
void CPicture::createSimpleRect(const Rect &r, bool screenFormat, ui32 color)
|
||||
{
|
||||
pos += r;
|
||||
pos += r.topLeft();
|
||||
pos.w = r.w;
|
||||
pos.h = r.h;
|
||||
if(screenFormat)
|
||||
|
@ -68,7 +68,7 @@ LRClickableAreaWText::LRClickableAreaWText()
|
||||
LRClickableAreaWText::LRClickableAreaWText(const Rect &Pos, const std::string &HoverText, const std::string &ClickText)
|
||||
{
|
||||
init();
|
||||
pos = Pos + pos;
|
||||
pos = Pos + pos.topLeft();
|
||||
hoverText = HoverText;
|
||||
text = ClickText;
|
||||
}
|
||||
@ -430,7 +430,7 @@ MoraleLuckBox::MoraleLuckBox(bool Morale, const Rect &r, bool Small)
|
||||
small(Small)
|
||||
{
|
||||
bonusValue = 0;
|
||||
pos = r + pos;
|
||||
pos = r + pos.topLeft();
|
||||
defActions = 255-DISPOSE;
|
||||
}
|
||||
|
||||
|
@ -473,7 +473,7 @@ CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList<void(c
|
||||
CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const std::string & bgName, const CFunctionList<void(const std::string &)> & CB)
|
||||
:cb(CB), CFocusable(std::make_shared<CKeyboardFocusListener>(this))
|
||||
{
|
||||
pos += Pos;
|
||||
pos += Pos.topLeft();
|
||||
pos.h = Pos.h;
|
||||
pos.w = Pos.w;
|
||||
|
||||
@ -490,7 +490,7 @@ CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const std::stri
|
||||
CTextInput::CTextInput(const Rect & Pos, SDL_Surface * srf)
|
||||
:CFocusable(std::make_shared<CKeyboardFocusListener>(this))
|
||||
{
|
||||
pos += Pos;
|
||||
pos += Pos.topLeft();
|
||||
captureAllKeys = true;
|
||||
OBJ_CONSTRUCTION;
|
||||
background = std::make_shared<CPicture>(Pos, 0, true);
|
||||
|
@ -1810,7 +1810,7 @@ void CAdvMapInt::tileRClicked(const int3 &mapPos)
|
||||
return;
|
||||
}
|
||||
|
||||
CRClickPopup::createAndPush(obj, GH.current->motion, ETextAlignment::CENTER);
|
||||
CRClickPopup::createAndPush(obj, Point(GH.current->motion), ETextAlignment::CENTER);
|
||||
}
|
||||
|
||||
void CAdvMapInt::enterCastingMode(const CSpell * sp)
|
||||
|
Loading…
Reference in New Issue
Block a user