1
0
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:
Ivan Savenko 2022-12-15 23:24:03 +02:00
parent 7e35a96055
commit 864990db13
30 changed files with 506 additions and 278 deletions

View File

@ -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-=;

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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)
{

View File

@ -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;
});
}

View File

@ -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();

View File

@ -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));

View File

@ -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;

View File

@ -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++)
{

View File

@ -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);

View File

@ -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
View 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
View 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);
};

View File

@ -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);
}

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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()

View File

@ -11,6 +11,7 @@
#include "ObjectLists.h"
#include "../../lib/FunctionList.h"
#include "../../lib/int3.h"
#include "Terrain.h"
VCMI_LIB_NAMESPACE_BEGIN

View File

@ -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());

View File

@ -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)

View File

@ -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;
}

View File

@ -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);

View File

@ -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)