瀏覽代碼

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

TODO: move color muxer effects from spells into spell config
Ivan Savenko 3 年之前
父節點
當前提交
41b87088d5

+ 6 - 69
client/battle/BattleAnimationClasses.cpp

@@ -617,81 +617,18 @@ 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)
 {
-	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);
+	auto effect = owner.effectsController->getMuxerEffect(colorFilterName);
+	steps = effect.filters;
+	timePoints = effect.timePoints;
 
-	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;
-}
+	assert(!steps.empty() && steps.size() == timePoints.size());
 
-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;
+	logAnim->debug("Created ColorTransformAnimation for %s", stack->getName());
 }
 
 RangedAttackAnimation::RangedAttackAnimation(BattleInterface & owner_, const CStack * attacker, BattleHex dest_, const CStack * defender)

+ 1 - 9
client/battle/BattleAnimationClasses.h

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

+ 33 - 1
client/battle/BattleEffectsController.cpp

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

+ 6 - 0
client/battle/BattleEffectsController.h

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

+ 2 - 2
client/battle/BattleInterface.cpp

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

+ 6 - 6
client/battle/BattleStacksController.cpp

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

+ 39 - 7
client/gui/ColorFilter.cpp

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

+ 12 - 5
client/gui/ColorFilter.h

@@ -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 - 0
config/battleEffects.json

@@ -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,
+			},
+		],
+	}
+}

+ 1 - 1
lib/battle/CUnitState.cpp

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