1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Color muxer effects can now be (partially) configured by user.

TODO: move color muxer effects from spells into spell config
This commit is contained in:
Ivan Savenko 2022-12-21 23:29:56 +02:00
parent 8a9a7b9650
commit 41b87088d5
10 changed files with 220 additions and 101 deletions

View File

@ -617,83 +617,20 @@ void ColorTransformAnimation::nextFrame()
owner.stacksController->setStackColorFilter(shifter, stack, spell, true);
}
ColorTransformAnimation::ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const CSpell * spell):
ColorTransformAnimation::ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const std::string & colorFilterName, const CSpell * spell):
BattleStackAnimation(owner, _stack),
spell(spell),
totalProgress(0.f)
{
auto effect = owner.effectsController->getMuxerEffect(colorFilterName);
steps = effect.filters;
timePoints = effect.timePoints;
assert(!steps.empty() && steps.size() == timePoints.size());
logAnim->debug("Created ColorTransformAnimation for %s", stack->getName());
}
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::summonAnimation(BattleInterface & owner, const CStack * stack)
{
auto result = teleportInAnimation(owner, stack);
result->timePoints.back() = 1.0f;
return result;
}
ColorTransformAnimation * ColorTransformAnimation::teleportInAnimation(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.0f);
result->timePoints.push_back(0.2f);
return result;
}
ColorTransformAnimation * ColorTransformAnimation::teleportOutAnimation(BattleInterface & owner, const CStack * stack)
{
auto result = teleportInAnimation(owner, stack);
std::swap(result->steps[0], result->steps[1]);
return result;
}
ColorTransformAnimation * ColorTransformAnimation::fadeOutAnimation(BattleInterface & owner, const CStack * stack)
{
auto result = teleportOutAnimation(owner, stack);
result->timePoints.back() = 1.0f;
return result;
}
RangedAttackAnimation::RangedAttackAnimation(BattleInterface & owner_, const CStack * attacker, BattleHex dest_, const CStack * defender)
: AttackAnimation(owner_, attacker, dest_, defender),
projectileEmitted(false)

View File

@ -123,16 +123,8 @@ class ColorTransformAnimation : public BattleStackAnimation
bool init() override;
void nextFrame() override;
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 * summonAnimation (BattleInterface & owner, const CStack * _stack);
static ColorTransformAnimation * fadeOutAnimation (BattleInterface & owner, const CStack * _stack);
static ColorTransformAnimation * teleportInAnimation (BattleInterface & owner, const CStack * _stack);
static ColorTransformAnimation * teleportOutAnimation (BattleInterface & owner, const CStack * _stack);
ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const std::string & colorFilterName, const CSpell * spell);
};
/// Base class for all animations that play during stack movement

View File

@ -26,6 +26,7 @@
#include "../../CCallback.h"
#include "../../lib/battle/BattleAction.h"
#include "../../lib/filesystem/ResourceID.h"
#include "../../lib/NetPacks.h"
#include "../../lib/CStack.h"
#include "../../lib/IGameEventsReceiver.h"
@ -33,7 +34,9 @@
BattleEffectsController::BattleEffectsController(BattleInterface & owner):
owner(owner)
{}
{
loadColorMuxers();
}
void BattleEffectsController::displayEffect(EBattleEffect effect, const BattleHex & destTile)
{
@ -132,3 +135,32 @@ void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer
});
}
}
void BattleEffectsController::loadColorMuxers()
{
const JsonNode config(ResourceID("config/battleEffects.json"));
for(auto & muxer : config["colorMuxers"].Struct())
{
ColorMuxerEffect effect;
std::string identifier = muxer.first;
for (const JsonNode & entry : muxer.second.Vector() )
{
effect.timePoints.push_back(entry["time"].Float());
effect.filters.push_back(ColorFilter::genFromJson(entry));
}
colorMuxerEffects[identifier] = effect;
}
}
const ColorMuxerEffect & BattleEffectsController::getMuxerEffect(const std::string & name)
{
static const ColorMuxerEffect emptyEffect;
if (colorMuxerEffects.count(name))
return colorMuxerEffects[name];
logAnim->error("Failed to find color muxer effect named '%s'!", name);
return emptyEffect;
}

View File

@ -19,6 +19,7 @@ struct BattleTriggerEffect;
VCMI_LIB_NAMESPACE_END
struct ColorMuxerEffect;
class CAnimation;
class Canvas;
class BattleInterface;
@ -43,7 +44,12 @@ class BattleEffectsController
/// list of current effects that are being displayed on screen (spells & creature abilities)
std::vector<BattleEffect> battleEffects;
std::map<std::string, ColorMuxerEffect> colorMuxerEffects;
void loadColorMuxers();
public:
const ColorMuxerEffect &getMuxerEffect(const std::string & name);
BattleEffectsController(BattleInterface & owner);
void startAction(const BattleAction* action);

View File

@ -389,9 +389,9 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
{
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
if (spellID == SpellID::BLOODLUST)
stacksController->addNewAnim( ColorTransformAnimation::bloodlustAnimation(*this, stack, spell));
stacksController->addNewAnim( new ColorTransformAnimation(*this, stack, "bloodlust", spell));
else if (spellID == SpellID::STONE_GAZE)
stacksController->addNewAnim( ColorTransformAnimation::petrifyAnimation(*this, stack, spell));
stacksController->addNewAnim( new ColorTransformAnimation(*this, stack, "petrification", spell));
else
displaySpellEffect(spellID, stack->getPosition());
});

View File

@ -223,9 +223,9 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]()
{
addNewAnim(ColorTransformAnimation::summonAnimation(owner, stack));
addNewAnim(new ColorTransformAnimation(owner, stack, "summonFadeIn", nullptr));
if (stack->isClone())
addNewAnim(ColorTransformAnimation::cloneAnimation(owner, stack, SpellID(SpellID::CLONE).toSpell()));
addNewAnim(new ColorTransformAnimation(owner, stack, "cloning", SpellID(SpellID::CLONE).toSpell()));
});
}
}
@ -468,7 +468,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
if (attackedInfo.killed && attackedInfo.defender->summoned)
{
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
addNewAnim(ColorTransformAnimation::fadeOutAnimation(owner, attackedInfo.defender));
addNewAnim(new ColorTransformAnimation(owner, attackedInfo.defender, "summonFadeOut", nullptr));
stackRemoved(attackedInfo.defender->ID);
});
}
@ -482,12 +482,12 @@ void BattleStacksController::stackTeleported(const CStack *stack, std::vector<Ba
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
addNewAnim( ColorTransformAnimation::teleportOutAnimation(owner, stack) );
addNewAnim( new ColorTransformAnimation(owner, stack, "teleportFadeOut", nullptr) );
});
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
stackAnimation[stack->ID]->pos.moveTo(getStackPositionAtHex(destHex.back(), stack));
addNewAnim( ColorTransformAnimation::teleportInAnimation(owner, stack) );
addNewAnim( new ColorTransformAnimation(owner, stack, "teleportFadeIn", nullptr) );
});
// animations will be executed by spell
@ -831,7 +831,7 @@ void BattleStacksController::removeExpiredColorFilters()
return false;
if (filter.effect == ColorFilter::genEmptyShifter())
return false;
if (filter.source && filter.target->hasBonus(Selector::source(Bonus::SPELL_EFFECT, filter.source->id)))
if (filter.source && filter.target->hasBonus(Selector::source(Bonus::SPELL_EFFECT, filter.source->id), Selector::all))
return false;
return true;
});

View File

@ -12,6 +12,8 @@
#include <SDL2/SDL_pixels.h>
#include "../../lib/JsonNode.h"
SDL_Color ColorFilter::shiftColor(const SDL_Color & in) const
{
SDL_Color out;
@ -45,13 +47,6 @@ ColorFilter ColorFilter::genAlphaShifter( float alpha )
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(
@ -113,3 +108,40 @@ ColorFilter ColorFilter::genCombined(const ColorFilter & left, const ColorFilter
float a = left.a * right.a;
return genMuxerShifter(r,g,b,a);
}
ColorFilter ColorFilter::genFromJson(const JsonNode & entry)
{
ChannelMuxer r{ 1.f, 0.f, 0.f, 0.f };
ChannelMuxer g{ 0.f, 1.f, 0.f, 0.f };
ChannelMuxer b{ 0.f, 0.f, 1.f, 0.f };
float a{ 1.0};
if (!entry["red"].isNull())
{
r.r = entry["red"].Vector()[0].Float();
r.g = entry["red"].Vector()[1].Float();
r.b = entry["red"].Vector()[2].Float();
r.a = entry["red"].Vector()[3].Float();
}
if (!entry["red"].isNull())
{
g.r = entry["green"].Vector()[0].Float();
g.g = entry["green"].Vector()[1].Float();
g.b = entry["green"].Vector()[2].Float();
g.a = entry["green"].Vector()[3].Float();
}
if (!entry["red"].isNull())
{
b.r = entry["blue"].Vector()[0].Float();
b.g = entry["blue"].Vector()[1].Float();
b.b = entry["blue"].Vector()[2].Float();
b.a = entry["blue"].Vector()[3].Float();
}
if (!entry["alpha"].isNull())
a = entry["alpha"].Float();
return genMuxerShifter(r,g,b,a);
}

View File

@ -8,10 +8,11 @@
*
*/
struct SDL_Color;
#pragma once
struct SDL_Color;
class JsonNode;
/// Base class for applying palette transformation on images
class ColorFilter
{
@ -38,9 +39,6 @@ public:
/// 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 );
@ -52,4 +50,13 @@ public:
/// Scales down strength of a shifter to a specified factor
static ColorFilter genInterpolated(const ColorFilter & left, const ColorFilter & right, float power);
/// Generates object using supplied Json config
static ColorFilter genFromJson(const JsonNode & entry);
};
struct ColorMuxerEffect
{
std::vector<ColorFilter> filters;
std::vector<float> timePoints;
};

113
config/battleEffects.json Normal file
View File

@ -0,0 +1,113 @@
{
"colorMuxers" : {
"example" : [
{
// Required. Time point at which this effect will be applied at full puwer.
// Must be greater than time point of previous effect
// During playback, effects will be played with smooth transition
// Effect will end once game reaches time point of the final filter
// Effect of final step will be applied to stack permanently for duration of the spell
// Note that actual speed of animation is subject to changes from in-game animation speed setting
"time" : 0.0,
// Optional. Transformation filter for red, green and blue components of a color
// Applies transformation with specified parameters to each channel. Formula:
// result = red * (value 1) + green * (value 2) + blue * (value 3) + (value 4)
"red" : [ 1.0, 0.0, 0.0, 0.0 ],
"green" : [ 0.0, 1.0, 0.0, 0.0 ],
"blue" : [ 0.0, 0.0, 1.0, 0.0 ],
/// Optional. Transparency filter, makes stack appear semi-transparent, used mostly for fade-in effects
/// Value 0 = full transparency, 1 = fully opaque
"alpha" : 1.0
}
],
"petrification" : [
{
"time" : 0.0
},
{
"time" : 1.0,
// Conversion to grayscale, using human eye perception factor for channels
"red" : [ 0.299, 0.587, 0.114, 0.0 ],
"green" : [ 0.299, 0.587, 0.114, 0.0 ],
"blue" : [ 0.299, 0.587, 0.114, 0.0 ],
}
],
"cloning" : [
{
// No fade in - will be handled by summonFadeIn effect
"time" : 0.0,
"red" : [ 0.5, 0.0, 0.0, 0.0 ],
"green" : [ 0.0, 0.5, 0.0, 0.0 ],
"blue" : [ 0.0, 0.0, 0.5, 0.5 ],
}
],
"summonFadeIn" : [
{
"time" : 0.0,
"alpha" : 0.0
},
{
"time" : 1.0
},
],
"summonFadeOut" : [
{
"time" : 0.0
},
{
"time" : 1.0,
"alpha" : 0.0
},
],
"teleportFadeIn" : [
{
"time" : 0.0,
"alpha" : 0.0
},
{
"time" : 0.2
},
],
"teleportFadeOut" : [
{
"time" : 0.0
},
{
"time" : 0.2,
"alpha" : 0.0
},
],
"bloodlust" : [
{
"time" : 0.0
},
{
"time" : 0.2,
"red" : [ 0.3, 0.0, 0.3, 0.4 ],
"green" : [ 0.0, 1.0, 0.0, 0.0 ],
"blue" : [ 0.0, 0.0, 1.0, 0.0 ],
"alpha" : 1.0
},
{
"time" : 0.4,
"red" : [ 0.3, 0.3, 0.3, 0.1 ],
"green" : [ 0.0, 0.5, 0.0, 0.0 ],
"blue" : [ 0.0, 0.0, 0.5, 0.0 ],
"alpha" : 1.0
},
{
"time" : 0.6,
"red" : [ 0.3, 0.0, 0.3, 0.4 ],
"green" : [ 0.0, 1.0, 0.0, 0.0 ],
"blue" : [ 0.0, 0.0, 1.0, 0.0 ],
"alpha" : 1.0
},
{
"time" : 0.8,
},
],
}
}

View File

@ -549,7 +549,7 @@ bool CUnitState::isGhost() const
bool CUnitState::isFrozen() const
{
return hasBonus(Selector::source(Bonus::SPELL_EFFECT, SpellID::STONE_GAZE));
return hasBonus(Selector::source(Bonus::SPELL_EFFECT, SpellID::STONE_GAZE), Selector::all);
}
bool CUnitState::isValidTarget(bool allowDead) const