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:
parent
8a9a7b9650
commit
41b87088d5
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
113
config/battleEffects.json
Normal 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,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user