Browse Source

Merge pull request #4935 from IvanSavenko/misc_fixes2

Miscellaneous fixes for reported issues
Ivan Savenko 11 months ago
parent
commit
f0a71c9e21
45 changed files with 274 additions and 257 deletions
  1. 1 1
      AI/Nullkiller/AIGateway.cpp
  2. 1 1
      AI/VCAI/VCAI.cpp
  3. 1 1
      client/ClientCommandManager.cpp
  4. 10 8
      client/battle/BattleAnimationClasses.cpp
  5. 4 3
      client/battle/BattleAnimationClasses.h
  6. 5 4
      client/battle/BattleEffectsController.cpp
  7. 3 2
      client/battle/BattleEffectsController.h
  8. 1 3
      client/battle/BattleFieldController.cpp
  9. 2 2
      client/battle/BattleInterface.cpp
  10. 1 1
      client/battle/BattleInterfaceClasses.cpp
  11. 4 4
      client/battle/BattleObstacleController.cpp
  12. 1 1
      client/battle/BattleStacksController.cpp
  13. 5 5
      client/battle/CreatureAnimation.cpp
  14. 15 22
      client/mapView/MapRenderer.cpp
  15. 1 1
      client/mapView/MapRenderer.h
  16. 23 6
      client/render/IImage.h
  17. 6 2
      client/render/ImageLocator.cpp
  18. 4 11
      client/render/ImageLocator.h
  19. 52 33
      client/renderSDL/ImageScaled.cpp
  20. 1 3
      client/renderSDL/ImageScaled.h
  21. 5 13
      client/renderSDL/RenderHandler.cpp
  22. 46 51
      client/renderSDL/SDLImage.cpp
  23. 1 12
      client/renderSDL/SDLImage.h
  24. 1 1
      client/renderSDL/SDL_Extensions.cpp
  25. 3 3
      client/widgets/CGarrisonInt.cpp
  26. 5 5
      client/widgets/Images.cpp
  27. 1 1
      client/windows/CCastleInterface.cpp
  28. 2 1
      client/windows/CHeroWindow.cpp
  29. 1 1
      client/windows/GUIClasses.cpp
  30. 2 1
      config/schemas/spell.json
  31. 1 1
      config/spells/ability.json
  32. 2 2
      config/spells/offensive.json
  33. 1 1
      config/spells/other.json
  34. 2 2
      config/spells/timed.json
  35. 1 1
      docs/modders/Entities_Format/Spell_Format.md
  36. 10 5
      docs/modders/HD_Graphics.md
  37. 2 2
      lib/CGameInfoCallback.cpp
  38. 12 8
      lib/StartInfo.cpp
  39. 2 2
      lib/StartInfo.h
  40. 8 17
      lib/entities/hero/CHeroClass.cpp
  41. 7 4
      lib/mapObjectConstructors/DwellingInstanceConstructor.cpp
  42. 2 1
      lib/mapObjects/CGHeroInstance.cpp
  43. 8 2
      lib/spells/CSpellHandler.cpp
  44. 1 0
      lib/spells/CSpellHandler.h
  45. 7 6
      server/CGameHandler.cpp

+ 1 - 1
AI/Nullkiller/AIGateway.cpp

@@ -763,7 +763,7 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
 	//you can't request action from action-response thread
 	//you can't request action from action-response thread
 	requestActionASAP([=]()
 	requestActionASAP([=]()
 	{
 	{
-		if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isSteadwickFallCampaignMission())
+		if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
 		{
 		{
 			pickBestCreatures(down, up);
 			pickBestCreatures(down, up);
 		}
 		}

+ 1 - 1
AI/VCAI/VCAI.cpp

@@ -731,7 +731,7 @@ void VCAI::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance *
 	//you can't request action from action-response thread
 	//you can't request action from action-response thread
 	requestActionASAP([=]()
 	requestActionASAP([=]()
 	{
 	{
-		if(removableUnits && !cb->getStartInfo()->isSteadwickFallCampaignMission())
+		if(removableUnits && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
 			pickBestCreatures(down, up);
 			pickBestCreatures(down, up);
 
 
 		answerQuery(queryID, 0);
 		answerQuery(queryID, 0);

+ 1 - 1
client/ClientCommandManager.cpp

@@ -394,7 +394,7 @@ void ClientCommandManager::handleDef2bmpCommand(std::istringstream& singleWordBu
 {
 {
 	std::string URI;
 	std::string URI;
 	singleWordBuffer >> URI;
 	singleWordBuffer >> URI;
-	auto anim = GH.renderHandler().loadAnimation(AnimationPath::builtin(URI), EImageBlitMode::ALPHA);
+	auto anim = GH.renderHandler().loadAnimation(AnimationPath::builtin(URI), EImageBlitMode::SIMPLE);
 	anim->exportBitmaps(VCMIDirs::get().userExtractedPath());
 	anim->exportBitmaps(VCMIDirs::get().userExtractedPath());
 }
 }
 
 

+ 10 - 8
client/battle/BattleAnimationClasses.cpp

@@ -881,9 +881,10 @@ uint32_t CastAnimation::getAttackClimaxFrame() const
 	return maxFrames / 2;
 	return maxFrames / 2;
 }
 }
 
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects, float transparencyFactor, bool reversed):
 	BattleAnimation(owner),
 	BattleAnimation(owner),
-	animation(GH.renderHandler().loadAnimation(animationName, EImageBlitMode::ALPHA)),
+	animation(GH.renderHandler().loadAnimation(animationName, EImageBlitMode::SIMPLE)),
+	transparencyFactor(transparencyFactor),
 	effectFlags(effects),
 	effectFlags(effects),
 	effectFinished(false),
 	effectFinished(false),
 	reversed(reversed)
 	reversed(reversed)
@@ -892,32 +893,32 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath &
 }
 }
 
 
 EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<BattleHex> hex, int effects, bool reversed):
 EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<BattleHex> hex, int effects, bool reversed):
-	EffectAnimation(owner, animationName, effects, reversed)
+	EffectAnimation(owner, animationName, effects, 1.0f, reversed)
 {
 {
 	battlehexes = hex;
 	battlehexes = hex;
 }
 }
 
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex, int effects, bool reversed):
-	EffectAnimation(owner, animationName, effects, reversed)
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex, int effects, float transparencyFactor, bool reversed):
+	EffectAnimation(owner, animationName, effects, transparencyFactor, reversed)
 {
 {
 	assert(hex.isValid());
 	assert(hex.isValid());
 	battlehexes.push_back(hex);
 	battlehexes.push_back(hex);
 }
 }
 
 
 EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<Point> pos, int effects, bool reversed):
 EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<Point> pos, int effects, bool reversed):
-	EffectAnimation(owner, animationName, effects, reversed)
+	EffectAnimation(owner, animationName, effects, 1.0f, reversed)
 {
 {
 	positions = pos;
 	positions = pos;
 }
 }
 
 
 EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, int effects, bool reversed):
 EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, int effects, bool reversed):
-	EffectAnimation(owner, animationName, effects, reversed)
+	EffectAnimation(owner, animationName, effects, 1.0f, reversed)
 {
 {
 	positions.push_back(pos);
 	positions.push_back(pos);
 }
 }
 
 
 EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex, int effects, bool reversed):
 EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex, int effects, bool reversed):
-	EffectAnimation(owner, animationName, effects, reversed)
+	EffectAnimation(owner, animationName, effects, 1.0f, reversed)
 {
 {
 	assert(hex.isValid());
 	assert(hex.isValid());
 	battlehexes.push_back(hex);
 	battlehexes.push_back(hex);
@@ -951,6 +952,7 @@ bool EffectAnimation::init()
 	be.effectID = ID;
 	be.effectID = ID;
 	be.animation = animation;
 	be.animation = animation;
 	be.currentFrame = 0;
 	be.currentFrame = 0;
+	be.transparencyFactor = transparencyFactor;
 	be.type = reversed ? BattleEffect::AnimType::REVERSE : BattleEffect::AnimType::DEFAULT;
 	be.type = reversed ? BattleEffect::AnimType::REVERSE : BattleEffect::AnimType::DEFAULT;
 
 
 	for (size_t i = 0; i < std::max(battlehexes.size(), positions.size()); ++i)
 	for (size_t i = 0; i < std::max(battlehexes.size(), positions.size()); ++i)

+ 4 - 3
client/battle/BattleAnimationClasses.h

@@ -309,9 +309,10 @@ public:
 class EffectAnimation : public BattleAnimation
 class EffectAnimation : public BattleAnimation
 {
 {
 	std::string soundName;
 	std::string soundName;
+	int effectFlags;
+	float transparencyFactor;
 	bool effectFinished;
 	bool effectFinished;
 	bool reversed;
 	bool reversed;
-	int effectFlags;
 
 
 	std::shared_ptr<CAnimation>	animation;
 	std::shared_ptr<CAnimation>	animation;
 	std::vector<Point> positions;
 	std::vector<Point> positions;
@@ -335,14 +336,14 @@ public:
 	};
 	};
 
 
 	/// Create animation with screen-wide effect
 	/// Create animation with screen-wide effect
-	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects = 0, float transparencyFactor = 1.f, bool reversed = false);
 
 
 	/// Create animation positioned at point(s). Note that positions must be are absolute, including battleint position offset
 	/// Create animation positioned at point(s). Note that positions must be are absolute, including battleint position offset
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos                 , int effects = 0, bool reversed = false);
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos                 , int effects = 0, bool reversed = false);
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<Point> pos    , int effects = 0, bool reversed = false);
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<Point> pos    , int effects = 0, bool reversed = false);
 
 
 	/// Create animation positioned at certain hex(es)
 	/// Create animation positioned at certain hex(es)
-	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex             , int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex             , int effects = 0, float transparencyFactor = 1.0f, bool reversed = false);
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<BattleHex> hex, int effects = 0, bool reversed = false);
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<BattleHex> hex, int effects = 0, bool reversed = false);
 
 
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex,   int effects = 0, bool reversed = false);
 	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex,   int effects = 0, bool reversed = false);

+ 5 - 4
client/battle/BattleEffectsController.cpp

@@ -44,7 +44,7 @@ void BattleEffectsController::displayEffect(EBattleEffect effect, const BattleHe
 	displayEffect(effect, AudioPath(), destTile);
 	displayEffect(effect, AudioPath(), destTile);
 }
 }
 
 
-void BattleEffectsController::displayEffect(EBattleEffect effect, const AudioPath & soundFile, const BattleHex & destTile)
+void BattleEffectsController::displayEffect(EBattleEffect effect, const AudioPath & soundFile, const BattleHex & destTile, float transparencyFactor)
 {
 {
 	size_t effectID = static_cast<size_t>(effect);
 	size_t effectID = static_cast<size_t>(effect);
 
 
@@ -52,7 +52,7 @@ void BattleEffectsController::displayEffect(EBattleEffect effect, const AudioPat
 
 
 	CCS->soundh->playSound( soundFile );
 	CCS->soundh->playSound( soundFile );
 
 
-	owner.stacksController->addNewAnim(new EffectAnimation(owner, customAnim, destTile));
+	owner.stacksController->addNewAnim(new EffectAnimation(owner, customAnim, destTile, 0, transparencyFactor));
 }
 }
 
 
 void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bte)
 void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bte)
@@ -69,7 +69,7 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
 	switch(static_cast<BonusType>(bte.effect))
 	switch(static_cast<BonusType>(bte.effect))
 	{
 	{
 		case BonusType::HP_REGENERATION:
 		case BonusType::HP_REGENERATION:
-			displayEffect(EBattleEffect::REGENERATION, AudioPath::builtin("REGENER"), stack->getPosition());
+			displayEffect(EBattleEffect::REGENERATION, AudioPath::builtin("REGENER"), stack->getPosition(), 0.5);
 			break;
 			break;
 		case BonusType::MANA_DRAIN:
 		case BonusType::MANA_DRAIN:
 			displayEffect(EBattleEffect::MANA_DRAIN, AudioPath::builtin("MANADRAI"), stack->getPosition());
 			displayEffect(EBattleEffect::MANA_DRAIN, AudioPath::builtin("MANADRAI"), stack->getPosition());
@@ -78,7 +78,7 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
 			displayEffect(EBattleEffect::POISON, AudioPath::builtin("POISON"), stack->getPosition());
 			displayEffect(EBattleEffect::POISON, AudioPath::builtin("POISON"), stack->getPosition());
 			break;
 			break;
 		case BonusType::FEAR:
 		case BonusType::FEAR:
-			displayEffect(EBattleEffect::FEAR, AudioPath::builtin("FEAR"), stack->getPosition());
+			displayEffect(EBattleEffect::FEAR, AudioPath::builtin("FEAR"), stack->getPosition(), 0.5);
 			break;
 			break;
 		case BonusType::MORALE:
 		case BonusType::MORALE:
 		{
 		{
@@ -124,6 +124,7 @@ void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer
 			currentFrame %= elem.animation->size();
 			currentFrame %= elem.animation->size();
 
 
 			auto img = elem.animation->getImage(currentFrame, static_cast<size_t>(elem.type));
 			auto img = elem.animation->getImage(currentFrame, static_cast<size_t>(elem.type));
+			img->setAlpha(255 * elem.transparencyFactor);
 
 
 			canvas.draw(img, elem.pos);
 			canvas.draw(img, elem.pos);
 		});
 		});

+ 3 - 2
client/battle/BattleEffectsController.h

@@ -39,7 +39,8 @@ struct BattleEffect
 
 
 	AnimType type;
 	AnimType type;
 	Point pos; //position on the screen
 	Point pos; //position on the screen
-	float currentFrame;
+	float currentFrame = 0.0;
+	float transparencyFactor = 1.0;
 	std::shared_ptr<CAnimation> animation;
 	std::shared_ptr<CAnimation> animation;
 	int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim
 	int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim
 	BattleHex tile; //Indicates if effect which hex the effect is drawn on
 	BattleHex tile; //Indicates if effect which hex the effect is drawn on
@@ -65,7 +66,7 @@ public:
 
 
 	//displays custom effect on the battlefield
 	//displays custom effect on the battlefield
 	void displayEffect(EBattleEffect effect, const BattleHex & destTile);
 	void displayEffect(EBattleEffect effect, const BattleHex & destTile);
-	void displayEffect(EBattleEffect effect, const AudioPath & soundFile, const BattleHex & destTile);
+	void displayEffect(EBattleEffect effect, const AudioPath & soundFile, const BattleHex & destTile, float transparencyFactor = 1.f);
 
 
 	void battleTriggerEffect(const BattleTriggerEffect & bte);
 	void battleTriggerEffect(const BattleTriggerEffect & bte);
 
 

+ 1 - 3
client/battle/BattleFieldController.cpp

@@ -114,7 +114,7 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 
 
 	//preparing cells and hexes
 	//preparing cells and hexes
 	cellBorder = GH.renderHandler().loadImage(ImagePath::builtin("CCELLGRD.BMP"), EImageBlitMode::COLORKEY);
 	cellBorder = GH.renderHandler().loadImage(ImagePath::builtin("CCELLGRD.BMP"), EImageBlitMode::COLORKEY);
-	cellShade = GH.renderHandler().loadImage(ImagePath::builtin("CCELLSHD.BMP"), EImageBlitMode::ALPHA);
+	cellShade = GH.renderHandler().loadImage(ImagePath::builtin("CCELLSHD.BMP"), EImageBlitMode::SIMPLE);
 	cellUnitMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMovementHighlight.PNG"), EImageBlitMode::COLORKEY);
 	cellUnitMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMovementHighlight.PNG"), EImageBlitMode::COLORKEY);
 	cellUnitMaxMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMaxMovementHighlight.PNG"), EImageBlitMode::COLORKEY);
 	cellUnitMaxMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMaxMovementHighlight.PNG"), EImageBlitMode::COLORKEY);
 
 
@@ -124,8 +124,6 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 	rangedFullDamageLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsGreen.json"), EImageBlitMode::COLORKEY);
 	rangedFullDamageLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsGreen.json"), EImageBlitMode::COLORKEY);
 	shootingRangeLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsRed.json"), EImageBlitMode::COLORKEY);
 	shootingRangeLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsRed.json"), EImageBlitMode::COLORKEY);
 
 
-	cellShade->setShadowEnabled(true);
-
 	if(!owner.siegeController)
 	if(!owner.siegeController)
 	{
 	{
 		auto bfieldType = owner.getBattle()->battleGetBattlefieldType();
 		auto bfieldType = owner.getBattle()->battleGetBattlefieldType();

+ 2 - 2
client/battle/BattleInterface.cpp

@@ -535,9 +535,9 @@ void BattleInterface::displaySpellAnimationQueue(const CSpell * spell, const CSp
 				flags |= EffectAnimation::SCREEN_FILL;
 				flags |= EffectAnimation::SCREEN_FILL;
 
 
 			if (!destinationTile.isValid())
 			if (!destinationTile.isValid())
-				stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, flags));
+				stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, flags, animation.transparency));
 			else
 			else
-				stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, destinationTile, flags));
+				stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, destinationTile, flags, animation.transparency));
 		}
 		}
 	}
 	}
 }
 }

+ 1 - 1
client/battle/BattleInterfaceClasses.cpp

@@ -398,7 +398,7 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 	else
 	else
 		animationPath = hero->getHeroClass()->imageBattleMale;
 		animationPath = hero->getHeroClass()->imageBattleMale;
 
 
-	animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::ALPHA);
+	animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::WITH_SHADOW);
 
 
 	pos.w = 64;
 	pos.w = 64;
 	pos.h = 136;
 	pos.h = 136;

+ 4 - 4
client/battle/BattleObstacleController.cpp

@@ -50,11 +50,11 @@ void BattleObstacleController::loadObstacleImage(const CObstacleInstance & oi)
 	if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
 	if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
 	{
 	{
 		// obstacle uses single bitmap image for animations
 		// obstacle uses single bitmap image for animations
-		obstacleImages[oi.uniqueID] = GH.renderHandler().loadImage(animationName.toType<EResType::IMAGE>(), EImageBlitMode::COLORKEY);
+		obstacleImages[oi.uniqueID] = GH.renderHandler().loadImage(animationName.toType<EResType::IMAGE>(), EImageBlitMode::SIMPLE);
 	}
 	}
 	else
 	else
 	{
 	{
-		obstacleAnimations[oi.uniqueID] = GH.renderHandler().loadAnimation(animationName, EImageBlitMode::COLORKEY);
+		obstacleAnimations[oi.uniqueID] = GH.renderHandler().loadAnimation(animationName, EImageBlitMode::SIMPLE);
 		obstacleImages[oi.uniqueID] = obstacleAnimations[oi.uniqueID]->getImage(0);
 		obstacleImages[oi.uniqueID] = obstacleAnimations[oi.uniqueID]->getImage(0);
 	}
 	}
 }
 }
@@ -78,7 +78,7 @@ void BattleObstacleController::obstacleRemoved(const std::vector<ObstacleChanges
 		if(animationPath.empty())
 		if(animationPath.empty())
 			continue;
 			continue;
 
 
-		auto animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::COLORKEY);
+		auto animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::SIMPLE);
 		auto first = animation->getImage(0, 0);
 		auto first = animation->getImage(0, 0);
 		if(!first)
 		if(!first)
 			continue;
 			continue;
@@ -105,7 +105,7 @@ void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<
 		if(!oi->visibleForSide(side, owner.getBattle()->battleHasNativeStack(side)))
 		if(!oi->visibleForSide(side, owner.getBattle()->battleHasNativeStack(side)))
 			continue;
 			continue;
 
 
-		auto animation = GH.renderHandler().loadAnimation(oi->getAppearAnimation(), EImageBlitMode::ALPHA);
+		auto animation = GH.renderHandler().loadAnimation(oi->getAppearAnimation(), EImageBlitMode::SIMPLE);
 		auto first = animation->getImage(0, 0);
 		auto first = animation->getImage(0, 0);
 		if(!first)
 		if(!first)
 			continue;
 			continue;

+ 1 - 1
client/battle/BattleStacksController.cpp

@@ -636,7 +636,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
 	{
 	{
 		owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=]()
 		owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=]()
 		{
 		{
-			owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, AudioPath::builtin("DRAINLIF"), attacker->getPosition());
+			owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, AudioPath::builtin("DRAINLIF"), attacker->getPosition(), 0.5);
 		});
 		});
 	}
 	}
 
 

+ 5 - 5
client/battle/CreatureAnimation.cpp

@@ -17,6 +17,7 @@
 #include "../render/CAnimation.h"
 #include "../render/CAnimation.h"
 #include "../render/Canvas.h"
 #include "../render/Canvas.h"
 #include "../render/ColorFilter.h"
 #include "../render/ColorFilter.h"
+#include "../render/Colors.h"
 #include "../render/IRenderHandler.h"
 #include "../render/IRenderHandler.h"
 
 
 static const ColorRGBA creatureBlueBorder = { 0, 255, 255, 255 };
 static const ColorRGBA creatureBlueBorder = { 0, 255, 255, 255 };
@@ -199,8 +200,8 @@ CreatureAnimation::CreatureAnimation(const AnimationPath & name_, TSpeedControll
 	  speedController(controller),
 	  speedController(controller),
 	  once(false)
 	  once(false)
 {
 {
-	forward = GH.renderHandler().loadAnimation(name_, EImageBlitMode::ALPHA);
-	reverse = GH.renderHandler().loadAnimation(name_, EImageBlitMode::ALPHA);
+	forward = GH.renderHandler().loadAnimation(name_, EImageBlitMode::WITH_SHADOW_AND_OVERLAY);
+	reverse = GH.renderHandler().loadAnimation(name_, EImageBlitMode::WITH_SHADOW_AND_OVERLAY);
 
 
 	// if necessary, add one frame into vcmi-only group DEAD
 	// if necessary, add one frame into vcmi-only group DEAD
 	if(forward->size(size_t(ECreatureAnimType::DEAD)) == 0)
 	if(forward->size(size_t(ECreatureAnimType::DEAD)) == 0)
@@ -339,15 +340,14 @@ void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter,
 
 
 	if(image)
 	if(image)
 	{
 	{
-		image->setShadowEnabled(true);
-		image->setOverlayEnabled(isIdle());
 		if (isIdle())
 		if (isIdle())
 			image->setOverlayColor(genBorderColor(getBorderStrength(elapsedTime), border));
 			image->setOverlayColor(genBorderColor(getBorderStrength(elapsedTime), border));
+		else
+			image->setOverlayColor(Colors::TRANSPARENCY);
 
 
 		image->adjustPalette(shifter, 0);
 		image->adjustPalette(shifter, 0);
 
 
 		canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
 		canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
-
 	}
 	}
 }
 }
 
 

+ 15 - 22
client/mapView/MapRenderer.cpp

@@ -316,7 +316,7 @@ uint8_t MapRendererBorder::checksum(IMapRendererContext & context, const int3 &
 MapRendererFow::MapRendererFow()
 MapRendererFow::MapRendererFow()
 {
 {
 	fogOfWarFullHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRC"), EImageBlitMode::OPAQUE);
 	fogOfWarFullHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRC"), EImageBlitMode::OPAQUE);
-	fogOfWarPartialHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRE"), EImageBlitMode::ALPHA);
+	fogOfWarPartialHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRE"), EImageBlitMode::SIMPLE);
 
 
 	static const std::vector<int> rotations = {22, 15, 2, 13, 12, 16, 28, 17, 20, 19, 7, 24, 26, 25, 30, 32, 27};
 	static const std::vector<int> rotations = {22, 15, 2, 13, 12, 16, 28, 17, 20, 19, 7, 24, 26, 25, 30, 32, 27};
 
 
@@ -383,24 +383,25 @@ std::shared_ptr<CAnimation> MapRendererObjects::getBaseAnimation(const CGObjectI
 	}
 	}
 
 
 	bool generateMovementGroups = (info->id == Obj::BOAT) || (info->id == Obj::HERO);
 	bool generateMovementGroups = (info->id == Obj::BOAT) || (info->id == Obj::HERO);
+	bool enableOverlay = obj->ID != Obj::BOAT && obj->ID != Obj::HERO && obj->getOwner() != PlayerColor::UNFLAGGABLE;
 
 
 	// Boat appearance files only contain single, unanimated image
 	// Boat appearance files only contain single, unanimated image
 	// proper boat animations are actually in different file
 	// proper boat animations are actually in different file
 	if (info->id == Obj::BOAT)
 	if (info->id == Obj::BOAT)
 		if(auto boat = dynamic_cast<const CGBoat*>(obj); boat && !boat->actualAnimation.empty())
 		if(auto boat = dynamic_cast<const CGBoat*>(obj); boat && !boat->actualAnimation.empty())
-			return getAnimation(boat->actualAnimation, generateMovementGroups);
+			return getAnimation(boat->actualAnimation, generateMovementGroups, enableOverlay);
 
 
-	return getAnimation(info->animationFile, generateMovementGroups);
+	return getAnimation(info->animationFile, generateMovementGroups, enableOverlay);
 }
 }
 
 
-std::shared_ptr<CAnimation> MapRendererObjects::getAnimation(const AnimationPath & filename, bool generateMovementGroups)
+std::shared_ptr<CAnimation> MapRendererObjects::getAnimation(const AnimationPath & filename, bool generateMovementGroups, bool enableOverlay)
 {
 {
 	auto it = animations.find(filename);
 	auto it = animations.find(filename);
 
 
 	if(it != animations.end())
 	if(it != animations.end())
 		return it->second;
 		return it->second;
 
 
-	auto ret = GH.renderHandler().loadAnimation(filename, EImageBlitMode::ALPHA);
+	auto ret = GH.renderHandler().loadAnimation(filename, enableOverlay ? EImageBlitMode::WITH_SHADOW_AND_OVERLAY : EImageBlitMode::WITH_SHADOW);
 	animations[filename] = ret;
 	animations[filename] = ret;
 
 
 	if(generateMovementGroups)
 	if(generateMovementGroups)
@@ -427,14 +428,14 @@ std::shared_ptr<CAnimation> MapRendererObjects::getFlagAnimation(const CGObjectI
 	{
 	{
 		assert(dynamic_cast<const CGHeroInstance *>(obj) != nullptr);
 		assert(dynamic_cast<const CGHeroInstance *>(obj) != nullptr);
 		assert(obj->tempOwner.isValidPlayer());
 		assert(obj->tempOwner.isValidPlayer());
-		return getAnimation(AnimationPath::builtin(heroFlags[obj->tempOwner.getNum()]), true);
+		return getAnimation(AnimationPath::builtin(heroFlags[obj->tempOwner.getNum()]), true, false);
 	}
 	}
 
 
 	if(obj->ID == Obj::BOAT)
 	if(obj->ID == Obj::BOAT)
 	{
 	{
 		const auto * boat = dynamic_cast<const CGBoat *>(obj);
 		const auto * boat = dynamic_cast<const CGBoat *>(obj);
 		if(boat && boat->hero && !boat->flagAnimations[boat->hero->tempOwner.getNum()].empty())
 		if(boat && boat->hero && !boat->flagAnimations[boat->hero->tempOwner.getNum()].empty())
-			return getAnimation(boat->flagAnimations[boat->hero->tempOwner.getNum()], true);
+			return getAnimation(boat->flagAnimations[boat->hero->tempOwner.getNum()], true, false);
 	}
 	}
 
 
 	return nullptr;
 	return nullptr;
@@ -447,7 +448,7 @@ std::shared_ptr<CAnimation> MapRendererObjects::getOverlayAnimation(const CGObje
 		// Boats have additional animation with waves around boat
 		// Boats have additional animation with waves around boat
 		const auto * boat = dynamic_cast<const CGBoat *>(obj);
 		const auto * boat = dynamic_cast<const CGBoat *>(obj);
 		if(boat && boat->hero && !boat->overlayAnimation.empty())
 		if(boat && boat->hero && !boat->overlayAnimation.empty())
-			return getAnimation(boat->overlayAnimation, true);
+			return getAnimation(boat->overlayAnimation, true, false);
 	}
 	}
 	return nullptr;
 	return nullptr;
 }
 }
@@ -478,22 +479,14 @@ void MapRendererObjects::renderImage(IMapRendererContext & context, Canvas & tar
 		return;
 		return;
 
 
 	image->setAlpha(transparency);
 	image->setAlpha(transparency);
-	image->setShadowEnabled(true);
-	if (object->ID != Obj::HERO)
+	if (object->ID != Obj::HERO) // heroes use separate image with flag instead of player-colored palette
 	{
 	{
-		image->setOverlayEnabled(object->getOwner().isValidPlayer() || object->getOwner() == PlayerColor::NEUTRAL);
-
 		if (object->getOwner().isValidPlayer())
 		if (object->getOwner().isValidPlayer())
 			image->setOverlayColor(graphics->playerColors[object->getOwner().getNum()]);
 			image->setOverlayColor(graphics->playerColors[object->getOwner().getNum()]);
 
 
 		if (object->getOwner() == PlayerColor::NEUTRAL)
 		if (object->getOwner() == PlayerColor::NEUTRAL)
 			image->setOverlayColor(graphics->neutralColor);
 			image->setOverlayColor(graphics->neutralColor);
 	}
 	}
-	else
-	{
-		// heroes use separate image with flag instead of player-colored palette
-		image->setOverlayEnabled(false);
-	}
 
 
 	Point offsetPixels = context.objectImageOffset(object->id, coordinates);
 	Point offsetPixels = context.objectImageOffset(object->id, coordinates);
 
 
@@ -567,10 +560,10 @@ uint8_t MapRendererObjects::checksum(IMapRendererContext & context, const int3 &
 }
 }
 
 
 MapRendererOverlay::MapRendererOverlay()
 MapRendererOverlay::MapRendererOverlay()
-	: imageGrid(GH.renderHandler().loadImage(ImagePath::builtin("debug/grid"), EImageBlitMode::ALPHA))
-	, imageBlocked(GH.renderHandler().loadImage(ImagePath::builtin("debug/blocked"), EImageBlitMode::ALPHA))
-	, imageVisitable(GH.renderHandler().loadImage(ImagePath::builtin("debug/visitable"), EImageBlitMode::ALPHA))
-	, imageSpellRange(GH.renderHandler().loadImage(ImagePath::builtin("debug/spellRange"), EImageBlitMode::ALPHA))
+	: imageGrid(GH.renderHandler().loadImage(ImagePath::builtin("debug/grid"), EImageBlitMode::COLORKEY))
+	, imageBlocked(GH.renderHandler().loadImage(ImagePath::builtin("debug/blocked"), EImageBlitMode::COLORKEY))
+	, imageVisitable(GH.renderHandler().loadImage(ImagePath::builtin("debug/visitable"), EImageBlitMode::COLORKEY))
+	, imageSpellRange(GH.renderHandler().loadImage(ImagePath::builtin("debug/spellRange"), EImageBlitMode::COLORKEY))
 {
 {
 
 
 }
 }
@@ -626,7 +619,7 @@ uint8_t MapRendererOverlay::checksum(IMapRendererContext & context, const int3 &
 }
 }
 
 
 MapRendererPath::MapRendererPath()
 MapRendererPath::MapRendererPath()
-	: pathNodes(GH.renderHandler().loadAnimation(AnimationPath::builtin("ADAG"), EImageBlitMode::ALPHA))
+	: pathNodes(GH.renderHandler().loadAnimation(AnimationPath::builtin("ADAG"), EImageBlitMode::SIMPLE))
 {
 {
 }
 }
 
 

+ 1 - 1
client/mapView/MapRenderer.h

@@ -77,7 +77,7 @@ class MapRendererObjects
 	std::shared_ptr<CAnimation> getFlagAnimation(const CGObjectInstance * obj);
 	std::shared_ptr<CAnimation> getFlagAnimation(const CGObjectInstance * obj);
 	std::shared_ptr<CAnimation> getOverlayAnimation(const CGObjectInstance * obj);
 	std::shared_ptr<CAnimation> getOverlayAnimation(const CGObjectInstance * obj);
 
 
-	std::shared_ptr<CAnimation> getAnimation(const AnimationPath & filename, bool generateMovementGroups);
+	std::shared_ptr<CAnimation> getAnimation(const AnimationPath & filename, bool generateMovementGroups, bool enableOverlay);
 
 
 	std::shared_ptr<IImage> getImage(IMapRendererContext & context, const CGObjectInstance * obj, const std::shared_ptr<CAnimation> & animation) const;
 	std::shared_ptr<IImage> getImage(IMapRendererContext & context, const CGObjectInstance * obj, const std::shared_ptr<CAnimation> & animation) const;
 
 

+ 23 - 6
client/render/IImage.h

@@ -37,9 +37,29 @@ enum class EImageBlitMode : uint8_t
 	/// RGBA: full alpha transparency range, e.g. shadows
 	/// RGBA: full alpha transparency range, e.g. shadows
 	COLORKEY,
 	COLORKEY,
 
 
-	/// Should be avoided if possible, use only for images that use def's with semi-transparency
-	/// Indexed or RGBA: Image might have full alpha transparency range, e.g. shadows
-	ALPHA
+	/// Full transparency including shadow, but treated as a single image
+	/// Indexed: Image can have alpha transparency, e.g. shadow
+	/// RGBA: full alpha transparency range, e.g. shadows
+	/// Upscaled form: single image, no option to display shadow separately
+	SIMPLE,
+
+	/// RGBA, may consist from 2 separate parts: base and shadow, overlay not preset or treated as part of body
+	WITH_SHADOW,
+
+	/// RGBA, may consist from 3 separate parts: base, shadow, and overlay
+	WITH_SHADOW_AND_OVERLAY,
+
+	/// RGBA, contains only body, with shadow and overlay disabled
+	ONLY_BODY,
+
+	/// RGBA, contains only body, with shadow disabled and overlay treated as part of body
+	ONLY_BODY_IGNORE_OVERLAY,
+
+	/// RGBA, contains only shadow
+	ONLY_SHADOW,
+
+	/// RGBA, contains only overlay
+	ONLY_OVERLAY,
 };
 };
 
 
 /// Base class for images for use in client code.
 /// Base class for images for use in client code.
@@ -75,9 +95,6 @@ public:
 	//only indexed bitmaps with 7 special colors
 	//only indexed bitmaps with 7 special colors
 	virtual void setOverlayColor(const ColorRGBA & color) = 0;
 	virtual void setOverlayColor(const ColorRGBA & color) = 0;
 
 
-	virtual void setShadowEnabled(bool on) = 0;
-	virtual void setBodyEnabled(bool on) = 0;
-	virtual void setOverlayEnabled(bool on) = 0;
 	virtual std::shared_ptr<const ISharedImage> getSharedImage() const = 0;
 	virtual std::shared_ptr<const ISharedImage> getSharedImage() const = 0;
 
 
 	virtual ~IImage() = default;
 	virtual ~IImage() = default;

+ 6 - 2
client/render/ImageLocator.cpp

@@ -124,8 +124,12 @@ std::string ImageLocator::toString() const
 	if (playerColored.isValidPlayer())
 	if (playerColored.isValidPlayer())
 		result += "-player" + playerColored.toString();
 		result += "-player" + playerColored.toString();
 
 
-	if (layer != EImageLayer::ALL)
-		result += "-layer" + std::to_string(static_cast<int>(layer));
+	if (layer == EImageBlitMode::ONLY_OVERLAY)
+		result += "-overlay";
+
+	if (layer == EImageBlitMode::ONLY_SHADOW)
+		result += "-shadow";
+
 
 
 	return result;
 	return result;
 }
 }

+ 4 - 11
client/render/ImageLocator.h

@@ -9,18 +9,11 @@
  */
  */
 #pragma once
 #pragma once
 
 
+#include "IImage.h"
+
 #include "../../lib/filesystem/ResourcePath.h"
 #include "../../lib/filesystem/ResourcePath.h"
 #include "../../lib/constants/EntityIdentifiers.h"
 #include "../../lib/constants/EntityIdentifiers.h"
 
 
-enum class EImageLayer
-{
-	ALL,
-
-	BODY,
-	SHADOW,
-	OVERLAY,
-};
-
 struct ImageLocator
 struct ImageLocator
 {
 {
 	std::optional<ImagePath> image;
 	std::optional<ImagePath> image;
@@ -28,13 +21,13 @@ struct ImageLocator
 	int defFrame = -1;
 	int defFrame = -1;
 	int defGroup = -1;
 	int defGroup = -1;
 
 
-	PlayerColor playerColored = PlayerColor::CANNOT_DETERMINE;
+	PlayerColor playerColored = PlayerColor::CANNOT_DETERMINE; // FIXME: treat as identical to blue to avoid double-loading?
 
 
 	bool verticalFlip = false;
 	bool verticalFlip = false;
 	bool horizontalFlip = false;
 	bool horizontalFlip = false;
 	int8_t scalingFactor = 0; // 0 = auto / use default scaling
 	int8_t scalingFactor = 0; // 0 = auto / use default scaling
 	int8_t preScaledFactor = 1;
 	int8_t preScaledFactor = 1;
-	EImageLayer layer = EImageLayer::ALL;
+	EImageBlitMode layer = EImageBlitMode::OPAQUE;
 
 
 	ImageLocator() = default;
 	ImageLocator() = default;
 	ImageLocator(const AnimationPath & path, int frame, int group);
 	ImageLocator(const AnimationPath & path, int frame, int group);

+ 52 - 33
client/renderSDL/ImageScaled.cpp

@@ -28,9 +28,7 @@ ImageScaled::ImageScaled(const ImageLocator & inputLocator, const std::shared_pt
 	, alphaValue(SDL_ALPHA_OPAQUE)
 	, alphaValue(SDL_ALPHA_OPAQUE)
 	, blitMode(mode)
 	, blitMode(mode)
 {
 {
-	setBodyEnabled(true);
-	if (mode == EImageBlitMode::ALPHA)
-		setShadowEnabled(true);
+	prepareImages();
 }
 }
 
 
 std::shared_ptr<const ISharedImage> ImageScaled::getSharedImage() const
 std::shared_ptr<const ISharedImage> ImageScaled::getSharedImage() const
@@ -92,8 +90,7 @@ void ImageScaled::setOverlayColor(const ColorRGBA & color)
 void ImageScaled::playerColored(PlayerColor player)
 void ImageScaled::playerColored(PlayerColor player)
 {
 {
 	playerColor = player;
 	playerColor = player;
-	if (body)
-		setBodyEnabled(true); // regenerate
+	prepareImages();
 }
 }
 
 
 void ImageScaled::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
 void ImageScaled::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
@@ -106,41 +103,63 @@ void ImageScaled::adjustPalette(const ColorFilter &shifter, uint32_t colorsToSki
 	// TODO: implement
 	// TODO: implement
 }
 }
 
 
-void ImageScaled::setShadowEnabled(bool on)
+void ImageScaled::prepareImages()
 {
 {
-	assert(blitMode == EImageBlitMode::ALPHA);
-	if (on)
+	switch(blitMode)
 	{
 	{
-		locator.layer = EImageLayer::SHADOW;
-		locator.playerColored = PlayerColor::CANNOT_DETERMINE;
-		shadow = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
+		case EImageBlitMode::OPAQUE:
+		case EImageBlitMode::COLORKEY:
+		case EImageBlitMode::SIMPLE:
+			locator.layer = blitMode;
+			locator.playerColored = playerColor;
+			body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
+			break;
+
+		case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
+		case EImageBlitMode::ONLY_BODY:
+			locator.layer = EImageBlitMode::ONLY_BODY;
+			locator.playerColored = playerColor;
+			body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
+			break;
+
+		case EImageBlitMode::WITH_SHADOW:
+		case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY:
+			locator.layer = EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY;
+			locator.playerColored = playerColor;
+			body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
+			break;
+
+		case EImageBlitMode::ONLY_SHADOW:
+		case EImageBlitMode::ONLY_OVERLAY:
+			body = nullptr;
+			break;
 	}
 	}
-	else
-		shadow = nullptr;
-}
 
 
-void ImageScaled::setBodyEnabled(bool on)
-{
-	if (on)
+	switch(blitMode)
 	{
 	{
-		locator.layer = blitMode == EImageBlitMode::ALPHA ? EImageLayer::BODY : EImageLayer::ALL;
-		locator.playerColored = playerColor;
-		body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
+		case EImageBlitMode::SIMPLE:
+		case EImageBlitMode::WITH_SHADOW:
+		case EImageBlitMode::ONLY_SHADOW:
+		case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
+			locator.layer = EImageBlitMode::ONLY_SHADOW;
+			locator.playerColored = PlayerColor::CANNOT_DETERMINE;
+			shadow = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
+			break;
+		default:
+			shadow = nullptr;
+			break;
 	}
 	}
-	else
-		body = nullptr;
-}
-
 
 
-void ImageScaled::setOverlayEnabled(bool on)
-{
-	assert(blitMode == EImageBlitMode::ALPHA);
-	if (on)
+	switch(blitMode)
 	{
 	{
-		locator.layer = EImageLayer::OVERLAY;
-		locator.playerColored = PlayerColor::CANNOT_DETERMINE;
-		overlay = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
+		case EImageBlitMode::ONLY_OVERLAY:
+		case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
+			locator.layer = EImageBlitMode::ONLY_OVERLAY;
+			locator.playerColored = PlayerColor::CANNOT_DETERMINE;
+			overlay = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
+			break;
+		default:
+			overlay = nullptr;
+			break;
 	}
 	}
-	else
-		overlay = nullptr;
 }
 }

+ 1 - 3
client/renderSDL/ImageScaled.h

@@ -44,6 +44,7 @@ private:
 	uint8_t alphaValue;
 	uint8_t alphaValue;
 	EImageBlitMode blitMode;
 	EImageBlitMode blitMode;
 
 
+	void prepareImages();
 public:
 public:
 	ImageScaled(const ImageLocator & locator, const std::shared_ptr<const ISharedImage> & source, EImageBlitMode mode);
 	ImageScaled(const ImageLocator & locator, const std::shared_ptr<const ISharedImage> & source, EImageBlitMode mode);
 
 
@@ -60,8 +61,5 @@ public:
 	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
 	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
 	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
 	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
 
 
-	void setShadowEnabled(bool on) override;
-	void setBodyEnabled(bool on) override;
-	void setOverlayEnabled(bool on) override;
 	std::shared_ptr<const ISharedImage> getSharedImage() const override;
 	std::shared_ptr<const ISharedImage> getSharedImage() const override;
 };
 };

+ 5 - 13
client/renderSDL/RenderHandler.cpp

@@ -303,22 +303,14 @@ std::shared_ptr<const ISharedImage> RenderHandler::scaleImage(const ImageLocator
 	if (imageFiles.count(locator))
 	if (imageFiles.count(locator))
 		return imageFiles.at(locator);
 		return imageFiles.at(locator);
 
 
-	auto handle = image->createImageReference(locator.layer == EImageLayer::ALL ? EImageBlitMode::OPAQUE : EImageBlitMode::ALPHA);
+	auto handle = image->createImageReference(locator.layer);
 
 
 	assert(locator.scalingFactor != 1); // should be filtered-out before
 	assert(locator.scalingFactor != 1); // should be filtered-out before
-
-	handle->setBodyEnabled(locator.layer == EImageLayer::ALL || locator.layer == EImageLayer::BODY);
-	if (locator.layer != EImageLayer::ALL)
-	{
-		handle->setOverlayEnabled(locator.layer == EImageLayer::OVERLAY);
-		handle->setShadowEnabled( locator.layer == EImageLayer::SHADOW);
-	}
-	if (locator.layer == EImageLayer::ALL && locator.playerColored != PlayerColor::CANNOT_DETERMINE)
+	if (locator.playerColored != PlayerColor::CANNOT_DETERMINE)
 		handle->playerColored(locator.playerColored);
 		handle->playerColored(locator.playerColored);
 
 
 	handle->scaleInteger(locator.scalingFactor);
 	handle->scaleInteger(locator.scalingFactor);
 
 
-	// TODO: try to optimize image size (possibly even before scaling?) - trim image borders if they are completely transparent
 	auto result = handle->getSharedImage();
 	auto result = handle->getSharedImage();
 	storeCachedImage(locator, result);
 	storeCachedImage(locator, result);
 	return result;
 	return result;
@@ -331,9 +323,9 @@ std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator, E
 	if(adjustedLocator.image)
 	if(adjustedLocator.image)
 	{
 	{
 		std::string imgPath = (*adjustedLocator.image).getName();
 		std::string imgPath = (*adjustedLocator.image).getName();
-		if(adjustedLocator.layer == EImageLayer::OVERLAY)
+		if(adjustedLocator.layer == EImageBlitMode::ONLY_OVERLAY)
 			imgPath += "-OVERLAY";
 			imgPath += "-OVERLAY";
-		if(adjustedLocator.layer == EImageLayer::SHADOW)
+		if(adjustedLocator.layer == EImageBlitMode::ONLY_SHADOW)
 			imgPath += "-SHADOW";
 			imgPath += "-SHADOW";
 
 
 		if(CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath)) ||
 		if(CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath)) ||
@@ -394,7 +386,7 @@ std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path, EImageB
 
 
 std::shared_ptr<IImage> RenderHandler::createImage(SDL_Surface * source)
 std::shared_ptr<IImage> RenderHandler::createImage(SDL_Surface * source)
 {
 {
-	return std::make_shared<SDLImageShared>(source)->createImageReference(EImageBlitMode::ALPHA);
+	return std::make_shared<SDLImageShared>(source)->createImageReference(EImageBlitMode::SIMPLE);
 }
 }
 
 
 std::shared_ptr<CAnimation> RenderHandler::loadAnimation(const AnimationPath & path, EImageBlitMode mode)
 std::shared_ptr<CAnimation> RenderHandler::loadAnimation(const AnimationPath & path, EImageBlitMode mode)

+ 46 - 51
client/renderSDL/SDLImage.cpp

@@ -139,6 +139,8 @@ SDLImageShared::SDLImageShared(const ImagePath & filename, int preScaleFactor)
 		savePalette();
 		savePalette();
 		fullSize.x = surf->w;
 		fullSize.x = surf->w;
 		fullSize.y = surf->h;
 		fullSize.y = surf->h;
+
+		optimizeSurface();
 	}
 	}
 }
 }
 
 
@@ -180,7 +182,7 @@ void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Poin
 	if (palette && surf->format->palette)
 	if (palette && surf->format->palette)
 		SDL_SetSurfacePalette(surf, palette);
 		SDL_SetSurfacePalette(surf, palette);
 
 
-	if(surf->format->palette && mode == EImageBlitMode::ALPHA)
+	if(surf->format->palette && mode != EImageBlitMode::OPAQUE && mode != EImageBlitMode::COLORKEY)
 	{
 	{
 		CSDL_Ext::blit8bppAlphaTo24bpp(surf, sourceRect, where, destShift, alpha);
 		CSDL_Ext::blit8bppAlphaTo24bpp(surf, sourceRect, where, destShift, alpha);
 	}
 	}
@@ -261,6 +263,13 @@ void SDLImageShared::optimizeSurface()
 		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
 		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
 		SDL_BlitSurface(surf, &rectSDL, newSurface, nullptr);
 		SDL_BlitSurface(surf, &rectSDL, newSurface, nullptr);
 
 
+		if (SDL_HasColorKey(surf))
+		{
+			uint32_t colorKey;
+			SDL_GetColorKey(surf, &colorKey);
+			SDL_SetColorKey(newSurface, SDL_TRUE, colorKey);
+		}
+
 		SDL_FreeSurface(surf);
 		SDL_FreeSurface(surf);
 		surf = newSurface;
 		surf = newSurface;
 
 
@@ -357,7 +366,7 @@ void SDLImageIndexed::playerColored(PlayerColor player)
 bool SDLImageShared::isTransparent(const Point & coords) const
 bool SDLImageShared::isTransparent(const Point & coords) const
 {
 {
 	if (surf)
 	if (surf)
-		return CSDL_Ext::isTransparent(surf, coords.x, coords.y);
+		return CSDL_Ext::isTransparent(surf, coords.x - margins.x, coords.y	- margins.y);
 	else
 	else
 		return true;
 		return true;
 }
 }
@@ -425,7 +434,7 @@ void SDLImageIndexed::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove,
 void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
 void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
 {
 {
 	// If shadow is enabled, following colors must be skipped unconditionally
 	// If shadow is enabled, following colors must be skipped unconditionally
-	if (shadowEnabled)
+	if (blitMode == EImageBlitMode::WITH_SHADOW || blitMode == EImageBlitMode::WITH_SHADOW_AND_OVERLAY)
 		colorsToSkipMask |= (1 << 0) + (1 << 1) + (1 << 4);
 		colorsToSkipMask |= (1 << 0) + (1 << 1) + (1 << 4);
 
 
 	// Note: here we skip first colors in the palette that are predefined in H3 images
 	// Note: here we skip first colors in the palette that are predefined in H3 images
@@ -445,15 +454,10 @@ SDLImageIndexed::SDLImageIndexed(const std::shared_ptr<const ISharedImage> & ima
 	:SDLImageBase::SDLImageBase(image, mode)
 	:SDLImageBase::SDLImageBase(image, mode)
 	,originalPalette(originalPalette)
 	,originalPalette(originalPalette)
 {
 {
-
 	currentPalette = SDL_AllocPalette(originalPalette->ncolors);
 	currentPalette = SDL_AllocPalette(originalPalette->ncolors);
 	SDL_SetPaletteColors(currentPalette, originalPalette->colors, 0, originalPalette->ncolors);
 	SDL_SetPaletteColors(currentPalette, originalPalette->colors, 0, originalPalette->ncolors);
 
 
-	if (mode == EImageBlitMode::ALPHA)
-	{
-		setOverlayColor(Colors::TRANSPARENCY);
-		setShadowTransparency(1.0);
-	}
+	preparePalette();
 }
 }
 
 
 SDLImageIndexed::~SDLImageIndexed()
 SDLImageIndexed::~SDLImageIndexed()
@@ -500,36 +504,42 @@ void SDLImageIndexed::setOverlayColor(const ColorRGBA & color)
 	}
 	}
 }
 }
 
 
-void SDLImageIndexed::setShadowEnabled(bool on)
-{
-	if (on)
-		setShadowTransparency(1.0);
-
-	if (!on && blitMode == EImageBlitMode::ALPHA)
-		setShadowTransparency(0.0);
-
-	shadowEnabled = on;
-}
-
-void SDLImageIndexed::setBodyEnabled(bool on)
-{
-	if (on)
-		adjustPalette(ColorFilter::genEmptyShifter(), 0);
-	else
-		adjustPalette(ColorFilter::genAlphaShifter(0), 0);
-
-	bodyEnabled = on;
-}
-
-void SDLImageIndexed::setOverlayEnabled(bool on)
+void SDLImageIndexed::preparePalette()
 {
 {
-	if (on)
-		setOverlayColor(Colors::WHITE_TRUE);
+	switch(blitMode)
+	{
+		case EImageBlitMode::ONLY_SHADOW:
+		case EImageBlitMode::ONLY_OVERLAY:
+			adjustPalette(ColorFilter::genAlphaShifter(0), 0);
+			break;
+	}
 
 
-	if (!on && blitMode == EImageBlitMode::ALPHA)
-		setOverlayColor(Colors::TRANSPARENCY);
+	switch(blitMode)
+	{
+		case EImageBlitMode::SIMPLE:
+		case EImageBlitMode::WITH_SHADOW:
+		case EImageBlitMode::ONLY_SHADOW:
+		case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
+			setShadowTransparency(1.0);
+			break;
+		case EImageBlitMode::ONLY_BODY:
+		case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY:
+		case EImageBlitMode::ONLY_OVERLAY:
+			setShadowTransparency(0.0);
+			break;
+	}
 
 
-	overlayEnabled = on;
+	switch(blitMode)
+	{
+		case EImageBlitMode::ONLY_OVERLAY:
+		case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
+			setOverlayColor(Colors::WHITE_TRUE);
+			break;
+		case EImageBlitMode::ONLY_SHADOW:
+		case EImageBlitMode::ONLY_BODY:
+			setOverlayColor(Colors::TRANSPARENCY);
+			break;
+	}
 }
 }
 
 
 SDLImageShared::~SDLImageShared()
 SDLImageShared::~SDLImageShared()
@@ -609,21 +619,6 @@ void SDLImageBase::setBlitMode(EImageBlitMode mode)
 	blitMode = mode;
 	blitMode = mode;
 }
 }
 
 
-void SDLImageRGB::setShadowEnabled(bool on)
-{
-	// Not supported. Theoretically we can try to extract all pixels of specific colors, but better to use 8-bit images or composite images
-}
-
-void SDLImageRGB::setBodyEnabled(bool on)
-{
-	// Not supported. Theoretically we can try to extract all pixels of specific colors, but better to use 8-bit images or composite images
-}
-
-void SDLImageRGB::setOverlayEnabled(bool on)
-{
-	// Not supported. Theoretically we can try to extract all pixels of specific colors, but better to use 8-bit images or composite images
-}
-
 void SDLImageRGB::setOverlayColor(const ColorRGBA & color)
 void SDLImageRGB::setOverlayColor(const ColorRGBA & color)
 {}
 {}
 
 

+ 1 - 12
client/renderSDL/SDLImage.h

@@ -89,11 +89,8 @@ class SDLImageIndexed final : public SDLImageBase
 	SDL_Palette * currentPalette = nullptr;
 	SDL_Palette * currentPalette = nullptr;
 	SDL_Palette * originalPalette = nullptr;
 	SDL_Palette * originalPalette = nullptr;
 
 
-	bool bodyEnabled = true;
-	bool shadowEnabled = false;
-	bool overlayEnabled = false;
-
 	void setShadowTransparency(float factor);
 	void setShadowTransparency(float factor);
+	void preparePalette();
 public:
 public:
 	SDLImageIndexed(const std::shared_ptr<const ISharedImage> & image, SDL_Palette * palette, EImageBlitMode mode);
 	SDLImageIndexed(const std::shared_ptr<const ISharedImage> & image, SDL_Palette * palette, EImageBlitMode mode);
 	~SDLImageIndexed();
 	~SDLImageIndexed();
@@ -106,10 +103,6 @@ public:
 	void scaleInteger(int factor) override;
 	void scaleInteger(int factor) override;
 	void scaleTo(const Point & size) override;
 	void scaleTo(const Point & size) override;
 	void exportBitmap(const boost::filesystem::path & path) const override;
 	void exportBitmap(const boost::filesystem::path & path) const override;
-
-	void setShadowEnabled(bool on) override;
-	void setBodyEnabled(bool on) override;
-	void setOverlayEnabled(bool on) override;
 };
 };
 
 
 class SDLImageRGB final : public SDLImageBase
 class SDLImageRGB final : public SDLImageBase
@@ -125,8 +118,4 @@ public:
 	void scaleInteger(int factor) override;
 	void scaleInteger(int factor) override;
 	void scaleTo(const Point & size) override;
 	void scaleTo(const Point & size) override;
 	void exportBitmap(const boost::filesystem::path & path) const override;
 	void exportBitmap(const boost::filesystem::path & path) const override;
-
-	void setShadowEnabled(bool on) override;
-	void setBodyEnabled(bool on) override;
-	void setOverlayEnabled(bool on) override;
 };
 };

+ 1 - 1
client/renderSDL/SDL_Extensions.cpp

@@ -90,7 +90,7 @@ SDL_Surface * CSDL_Ext::newSurface(const Point & dimensions, SDL_Surface * mod)
 	if (mod->format->palette)
 	if (mod->format->palette)
 	{
 	{
 		assert(ret->format->palette);
 		assert(ret->format->palette);
-		assert(ret->format->palette->ncolors == mod->format->palette->ncolors);
+		assert(ret->format->palette->ncolors >= mod->format->palette->ncolors);
 		memcpy(ret->format->palette->colors, mod->format->palette->colors, mod->format->palette->ncolors * sizeof(SDL_Color));
 		memcpy(ret->format->palette->colors, mod->format->palette->colors, mod->format->palette->ncolors * sizeof(SDL_Color));
 	}
 	}
 	return ret;
 	return ret;

+ 3 - 3
client/widgets/CGarrisonInt.cpp

@@ -274,12 +274,12 @@ bool CGarrisonSlot::mustForceReselection() const
 	if (!LOCPLINT->makingTurn)
 	if (!LOCPLINT->makingTurn)
 		return true;
 		return true;
 
 
-	if (!creature || !selection->creature)
-		return false;
-
 	// Attempt to take creatures from ally (select theirs first)
 	// Attempt to take creatures from ally (select theirs first)
 	if (!selection->our())
 	if (!selection->our())
 		return true;
 		return true;
+	
+	if (!creature || !selection->creature)
+		return false;
 
 
 	// Attempt to swap creatures with ally (select ours first)
 	// Attempt to swap creatures with ally (select ours first)
 	if (selection->creature != creature && withAlly)
 	if (selection->creature != creature && withAlly)

+ 5 - 5
client/widgets/Images.cpp

@@ -20,6 +20,7 @@
 #include "../render/CAnimation.h"
 #include "../render/CAnimation.h"
 #include "../render/Canvas.h"
 #include "../render/Canvas.h"
 #include "../render/ColorFilter.h"
 #include "../render/ColorFilter.h"
+#include "../render/Colors.h"
 
 
 #include "../battle/BattleInterface.h"
 #include "../battle/BattleInterface.h"
 #include "../battle/BattleInterfaceClasses.h"
 #include "../battle/BattleInterfaceClasses.h"
@@ -194,12 +195,12 @@ CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, size_t Group, i
 {
 {
 	pos.x += x;
 	pos.x += x;
 	pos.y += y;
 	pos.y += y;
-	anim = GH.renderHandler().loadAnimation(name, EImageBlitMode::COLORKEY);
+	anim = GH.renderHandler().loadAnimation(name, (Flags & CCreatureAnim::CREATURE_MODE) ? EImageBlitMode::WITH_SHADOW_AND_OVERLAY : EImageBlitMode::COLORKEY);
 	init();
 	init();
 }
 }
 
 
 CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, Rect targetPos, size_t Group, ui8 Flags):
 CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, Rect targetPos, size_t Group, ui8 Flags):
-	anim(GH.renderHandler().loadAnimation(name, EImageBlitMode::COLORKEY)),
+	anim(GH.renderHandler().loadAnimation(name, (Flags & CCreatureAnim::CREATURE_MODE) ? EImageBlitMode::WITH_SHADOW_AND_OVERLAY : EImageBlitMode::COLORKEY)),
 	frame(Frame),
 	frame(Frame),
 	group(Group),
 	group(Group),
 	flags(Flags),
 	flags(Flags),
@@ -317,7 +318,7 @@ bool CAnimImage::isPlayerColored() const
 }
 }
 
 
 CShowableAnim::CShowableAnim(int x, int y, const AnimationPath & name, ui8 Flags, ui32 frameTime, size_t Group, uint8_t alpha):
 CShowableAnim::CShowableAnim(int x, int y, const AnimationPath & name, ui8 Flags, ui32 frameTime, size_t Group, uint8_t alpha):
-	anim(GH.renderHandler().loadAnimation(name, (Flags & CREATURE_MODE) ? EImageBlitMode::ALPHA : EImageBlitMode::COLORKEY)),
+	anim(GH.renderHandler().loadAnimation(name, (Flags & CREATURE_MODE) ? EImageBlitMode::WITH_SHADOW_AND_OVERLAY : EImageBlitMode::COLORKEY)),
 	group(Group),
 	group(Group),
 	frame(0),
 	frame(0),
 	first(0),
 	first(0),
@@ -430,9 +431,8 @@ void CShowableAnim::blitImage(size_t frame, size_t group, Canvas & to)
 	auto img = anim->getImage(frame, group);
 	auto img = anim->getImage(frame, group);
 	if(img)
 	if(img)
 	{
 	{
-		if (flags & CREATURE_MODE)
-			img->setShadowEnabled(true);
 		img->setAlpha(alpha);
 		img->setAlpha(alpha);
+		img->setOverlayColor(Colors::TRANSPARENCY);
 		to.draw(img, pos.topLeft(), src);
 		to.draw(img, pos.topLeft(), src);
 	}
 	}
 }
 }

+ 1 - 1
client/windows/CCastleInterface.cpp

@@ -98,7 +98,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town
 		border = GH.renderHandler().loadImage(str->borderName, EImageBlitMode::COLORKEY);
 		border = GH.renderHandler().loadImage(str->borderName, EImageBlitMode::COLORKEY);
 
 
 	if(!str->areaName.empty())
 	if(!str->areaName.empty())
-		area = GH.renderHandler().loadImage(str->areaName, EImageBlitMode::ALPHA);
+		area = GH.renderHandler().loadImage(str->areaName, EImageBlitMode::SIMPLE);
 }
 }
 
 
 const CBuilding * CBuildingRect::getBuilding()
 const CBuilding * CBuildingRect::getBuilding()

+ 2 - 1
client/windows/CHeroWindow.cpp

@@ -199,10 +199,11 @@ void CHeroWindow::update()
 		OBJECT_CONSTRUCTION;
 		OBJECT_CONSTRUCTION;
 		if(!garr)
 		if(!garr)
 		{
 		{
+			bool removableTroops = curHero->getOwner() == LOCPLINT->playerID;
 			std::string helpBox = heroscrn[32];
 			std::string helpBox = heroscrn[32];
 			boost::algorithm::replace_first(helpBox, "%s", CGI->generaltexth->allTexts[43]);
 			boost::algorithm::replace_first(helpBox, "%s", CGI->generaltexth->allTexts[43]);
 
 
-			garr = std::make_shared<CGarrisonInt>(Point(15, 485), 8, Point(), curHero);
+			garr = std::make_shared<CGarrisonInt>(Point(15, 485), 8, Point(), curHero, nullptr, removableTroops);
 			auto split = std::make_shared<CButton>(Point(539, 519), AnimationPath::builtin("hsbtns9.def"), CButton::tooltip(CGI->generaltexth->allTexts[256], helpBox), [this](){ garr->splitClick(); }, EShortcut::HERO_ARMY_SPLIT);
 			auto split = std::make_shared<CButton>(Point(539, 519), AnimationPath::builtin("hsbtns9.def"), CButton::tooltip(CGI->generaltexth->allTexts[256], helpBox), [this](){ garr->splitClick(); }, EShortcut::HERO_ARMY_SPLIT);
 			garr->addSplitBtn(split);
 			garr->addSplitBtn(split);
 		}
 		}

+ 1 - 1
client/windows/GUIClasses.cpp

@@ -950,7 +950,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID bu
 	}
 	}
 	else if(auto uni = dynamic_cast<const CGUniversity *>(_market); uni->appearance)
 	else if(auto uni = dynamic_cast<const CGUniversity *>(_market); uni->appearance)
 	{
 	{
-		titlePic = std::make_shared<CAnimImage>(uni->appearance->animationFile, 0);
+		titlePic = std::make_shared<CAnimImage>(uni->appearance->animationFile, 0, 0, 0, 0, CShowableAnim::CREATURE_MODE);
 		titleStr = uni->title;
 		titleStr = uni->title;
 		speechStr = uni->speech;
 		speechStr = uni->speech;
 	}
 	}

+ 2 - 1
config/schemas/spell.json

@@ -22,7 +22,8 @@
 						"properties" : {
 						"properties" : {
 							"verticalPosition" : {"type" : "string", "enum" :["top","bottom"]},
 							"verticalPosition" : {"type" : "string", "enum" :["top","bottom"]},
 							"defName" : {"type" : "string", "format" : "animationFile"},
 							"defName" : {"type" : "string", "format" : "animationFile"},
-							"effectName" : { "type" : "string" }
+							"effectName" : { "type" : "string" },
+							"transparency" : {"type" : "number", "minimum" : 0, "maximum" : 1}
 						},
 						},
 						"additionalProperties" : false
 						"additionalProperties" : false
 					}
 					}

+ 1 - 1
config/spells/ability.json

@@ -252,7 +252,7 @@
 		"targetType": "NO_TARGET",
 		"targetType": "NO_TARGET",
 
 
 		"animation":{
 		"animation":{
-			"hit":["SP04_"]
+			"hit":[{ "defName" : "SP04_", "transparency" : 0.5}]
 		},
 		},
 		"sounds": {
 		"sounds": {
 			"cast": "DEATHCLD"
 			"cast": "DEATHCLD"

+ 2 - 2
config/spells/offensive.json

@@ -44,7 +44,7 @@
 				{"minimumAngle": 1.20 ,"defName":"C08SPW1"},
 				{"minimumAngle": 1.20 ,"defName":"C08SPW1"},
 				{"minimumAngle": 1.50 ,"defName":"C08SPW0"}
 				{"minimumAngle": 1.50 ,"defName":"C08SPW0"}
 			],
 			],
-			"hit":["C08SPW5"]
+			"hit":[ {"defName" : "C08SPW5", "transparency" : 0.5 }]
 		},
 		},
 		"sounds": {
 		"sounds": {
 			"cast": "ICERAY"
 			"cast": "ICERAY"
@@ -309,7 +309,7 @@
 		"targetType" : "CREATURE",
 		"targetType" : "CREATURE",
 
 
 		"animation":{
 		"animation":{
-			"affect":["C14SPA0"]
+			"affect":[{"defName" : "C14SPA0", "transparency" : 0.5}]
 		},
 		},
 		"sounds": {
 		"sounds": {
 			"cast": "SACBRETH"
 			"cast": "SACBRETH"

+ 1 - 1
config/spells/other.json

@@ -483,7 +483,7 @@
 		"targetType" : "CREATURE",
 		"targetType" : "CREATURE",
 
 
 		"animation":{
 		"animation":{
-			"affect":["C01SPE0"]
+			"affect":[{ "defName" : "C01SPE0", "transparency" : 0.5}]
 		},
 		},
 		"sounds": {
 		"sounds": {
 			"cast": "RESURECT"
 			"cast": "RESURECT"

+ 2 - 2
config/spells/timed.json

@@ -652,7 +652,7 @@
 		"targetType" : "CREATURE",
 		"targetType" : "CREATURE",
 
 
 		"animation":{
 		"animation":{
-			"affect":["C07SPA1"],
+			"affect":[{"defName" : "C07SPA1", "transparency" : 0.5}],
 			"projectile":[{"defName":"C07SPA0"}]//???
 			"projectile":[{"defName":"C07SPA0"}]//???
 		},
 		},
 		"sounds": {
 		"sounds": {
@@ -696,7 +696,7 @@
 		"targetType" : "CREATURE",
 		"targetType" : "CREATURE",
 
 
 		"animation":{
 		"animation":{
-			"affect":[{"defName":"C10SPW", "verticalPosition":"bottom"}]
+			"affect":[{"defName":"C10SPW", "verticalPosition":"bottom", "transparency" : 0.5}]
 		},
 		},
 		"sounds": {
 		"sounds": {
 			"cast": "PRAYER"
 			"cast": "PRAYER"

+ 1 - 1
docs/modders/Entities_Format/Spell_Format.md

@@ -167,7 +167,7 @@ TODO
 	],
 	],
 	"cast" : []
 	"cast" : []
 	"hit":["C20SPX"],
 	"hit":["C20SPX"],
-	"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
+	"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom", "transparency" : 0.5}, "C11SPA1"]
 }
 }
 ```
 ```
 
 

+ 10 - 5
docs/modders/HD_Graphics.md

@@ -1,10 +1,10 @@
 # HD Graphics
 # HD Graphics
 
 
-It's possible to provide alternative HD-Graphics within mods. They will be used if any upscaling filter is activated.
+It's possible to provide alternative high-definition graphics within mods. They will be used if any upscaling filter is activated.
 
 
 ## Preconditions
 ## Preconditions
 
 
-It's still necessary to add 1x graphics as before. HD graphics are seperate from usual graphics. This allows to partitially use HD for a few graphics in mod. And avoid handling huge graphics if upscaling isn't enabled.
+It's still necessary to add 1x standard definition graphics as before. HD graphics are seperate from usual graphics. This allows to partitially use HD for a few graphics in mod. And avoid handling huge graphics if upscaling isn't enabled.
 
 
 Currently following scaling factors are possible to use: 2x, 3x, 4x. You can also provide multiple of them (increases size of mod, but improves loading performance for player). It's recommend to provide 2x and 3x images.
 Currently following scaling factors are possible to use: 2x, 3x, 4x. You can also provide multiple of them (increases size of mod, but improves loading performance for player). It's recommend to provide 2x and 3x images.
 
 
@@ -20,10 +20,15 @@ The sprites should have the same name and folder structure as in `sprites` and `
 
 
 ### Shadows / Overlays
 ### Shadows / Overlays
 
 
-It's also possible (but not necessary) to add HD shadows: Just place a image next to the normal upscaled image with the suffix `-shadow`. E.g. `TestImage.png` and `TestImage-shadow.png`.
+It's also possible (but not necessary) to add high-definition shadows: Just place a image next to the normal upscaled image with the suffix `-shadow`. E.g. `TestImage.png` and `TestImage-shadow.png`.
+In future, such shadows will likely become required to correctly exclude shadow from effects such as Clone spell.
+
+Shadow images are used only for animations of following objects:
+- All adventure map objects
+- All creature animations in combat
 
 
 Same for overlays with `-overlay`. But overlays are **necessary** for some animation graphics. They will be colorized by VCMI.
 Same for overlays with `-overlay`. But overlays are **necessary** for some animation graphics. They will be colorized by VCMI.
 
 
 Currently needed for:
 Currently needed for:
-- flaggable adventure map objects (needs a transparent image with white flags on it)
-- creature battle animations (needs a transparent image with white outline of creature for highlighting on mouse hover)
+- Flaggable adventure map objects. Overlay must contain a transparent image with white flags on it and will be used to colorize flags to owning player
+- Creature battle animations, idle and mouse hover group. Overlay must contain a transparent image with white outline of creature for highlighting on mouse hover)

+ 2 - 2
lib/CGameInfoCallback.cpp

@@ -957,12 +957,12 @@ void CGameInfoCallback::calculatePaths( const CGHeroInstance *hero, CPathsInfo &
 
 
 const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const
 const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const
 {
 {
-	return gs->map->artInstances[aid.num];
+	return gs->map->artInstances.at(aid.num);
 }
 }
 
 
 const CGObjectInstance * CGameInfoCallback::getObjInstance( ObjectInstanceID oid ) const
 const CGObjectInstance * CGameInfoCallback::getObjInstance( ObjectInstanceID oid ) const
 {
 {
-	return gs->map->objects[oid.num];
+	return gs->map->objects.at(oid.num);
 }
 }
 
 
 const CArtifactSet * CGameInfoCallback::getArtSet(const ArtifactLocation & loc) const
 const CArtifactSet * CGameInfoCallback::getArtSet(const ArtifactLocation & loc) const

+ 12 - 8
lib/StartInfo.cpp

@@ -90,18 +90,22 @@ std::string StartInfo::getCampaignName() const
 		return VLC->generaltexth->allTexts[508];
 		return VLC->generaltexth->allTexts[508];
 }
 }
 
 
-bool StartInfo::isSteadwickFallCampaignMission() const
+bool StartInfo::isRestorationOfErathiaCampaign() const
 {
 {
-	if (!campState)
-		return false;
+	constexpr std::array roeCampaigns = {
+		"DATA/GOOD1",
+		"DATA/EVIL1",
+		"DATA/GOOD2",
+		"DATA/NEUTRAL1",
+		"DATA/EVIL2",
+		"DATA/GOOD3",
+		"DATA/SECRET1",
+	};
 
 
-	if (campState->getFilename() != "DATA/EVIL1")
-		return false;
-
-	if (campState->currentScenario() != CampaignScenarioID(2))
+	if (!campState)
 		return false;
 		return false;
 
 
-	return true;
+	return vstd::contains(roeCampaigns, campState->getFilename());
 }
 }
 
 
 void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const
 void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const

+ 2 - 2
lib/StartInfo.h

@@ -164,8 +164,8 @@ struct DLL_LINKAGE StartInfo : public Serializeable
 	// TODO: Must be client-side
 	// TODO: Must be client-side
 	std::string getCampaignName() const;
 	std::string getCampaignName() const;
 
 
-	/// Controls hardcoded check for "Steadwick's Fall" scenario from "Dungeon and Devils" campaign
-	bool isSteadwickFallCampaignMission() const;
+	/// Controls hardcoded check for handling of garrisons by AI in Restoration of Erathia campaigns to match H3 behavior
+	bool isRestorationOfErathiaCampaign() const;
 
 
 	template <typename Handler>
 	template <typename Handler>
 	void serialize(Handler &h)
 	void serialize(Handler &h)

+ 8 - 17
lib/entities/hero/CHeroClass.cpp

@@ -23,29 +23,20 @@ SecondarySkill CHeroClass::chooseSecSkill(const std::set<SecondarySkill> & possi
 {
 {
 	assert(!possibles.empty());
 	assert(!possibles.empty());
 
 
-	if (possibles.size() == 1)
-		return *possibles.begin();
+	std::vector<int> weights;
+	std::vector<SecondarySkill> skills;
 
 
-	int totalProb = 0;
-	for(const auto & possible : possibles)
-		if (secSkillProbability.count(possible) != 0)
-			totalProb += secSkillProbability.at(possible);
-
-	if (totalProb == 0) // may trigger if set contains only banned skills (0 probability)
-		return *RandomGeneratorUtil::nextItem(possibles, rand);
-
-	auto ran = rand.nextInt(totalProb - 1);
 	for(const auto & possible : possibles)
 	for(const auto & possible : possibles)
 	{
 	{
+		skills.push_back(possible);
 		if (secSkillProbability.count(possible) != 0)
 		if (secSkillProbability.count(possible) != 0)
-			ran -= secSkillProbability.at(possible);
-
-		if(ran < 0)
-			return possible;
+			weights.push_back(secSkillProbability.at(possible));
+		else
+			weights.push_back(1); // H3 behavior - banned skills have minimal (1) chance to be picked
 	}
 	}
 
 
-	assert(0); // should not be possible
-	return *possibles.begin();
+	int selectedIndex = RandomGeneratorUtil::nextItemWeighted(weights, rand);
+	return skills.at(selectedIndex);
 }
 }
 
 
 bool CHeroClass::isMagicHero() const
 bool CHeroClass::isMagicHero() const

+ 7 - 4
lib/mapObjectConstructors/DwellingInstanceConstructor.cpp

@@ -88,25 +88,28 @@ void DwellingInstanceConstructor::randomizeObject(CGDwelling * dwelling, vstd::R
 			dwelling->creatures.back().second.push_back(cre->getId());
 			dwelling->creatures.back().second.push_back(cre->getId());
 	}
 	}
 
 
-	bool guarded = false; //TODO: serialize for sanity
+	bool guarded = false;
 
 
-	if(guards.getType() == JsonNode::JsonType::DATA_BOOL) //simple switch
+	if(guards.getType() == JsonNode::JsonType::DATA_BOOL)
 	{
 	{
+		//simple switch
 		if(guards.Bool())
 		if(guards.Bool())
 		{
 		{
 			guarded = true;
 			guarded = true;
 		}
 		}
 	}
 	}
-	else if(guards.getType() == JsonNode::JsonType::DATA_VECTOR) //custom guards (eg. Elemental Conflux)
+	else if(guards.getType() == JsonNode::JsonType::DATA_VECTOR)
 	{
 	{
+		//custom guards (eg. Elemental Conflux)
 		JsonRandom::Variables emptyVariables;
 		JsonRandom::Variables emptyVariables;
 		for(auto & stack : randomizer.loadCreatures(guards, rng, emptyVariables))
 		for(auto & stack : randomizer.loadCreatures(guards, rng, emptyVariables))
 		{
 		{
 			dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.getId(), stack.count));
 			dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.getId(), stack.count));
 		}
 		}
 	}
 	}
-	else //default condition - creatures are of level 5 or higher
+	else if (dwelling->ID == Obj::CREATURE_GENERATOR1 || dwelling->ID == Obj::CREATURE_GENERATOR4)
 	{
 	{
+		//default condition - this is dwelling with creatures of level 5 or higher
 		for(auto creatureEntry : availableCreatures)
 		for(auto creatureEntry : availableCreatures)
 		{
 		{
 			if(creatureEntry.at(0)->getLevel() >= 5)
 			if(creatureEntry.at(0)->getLevel() >= 5)

+ 2 - 1
lib/mapObjects/CGHeroInstance.cpp

@@ -338,7 +338,8 @@ void CGHeroInstance::setHeroType(HeroTypeID heroType)
 
 
 void CGHeroInstance::initObj(vstd::RNG & rand)
 void CGHeroInstance::initObj(vstd::RNG & rand)
 {
 {
-	updateAppearance();
+	if (ID == Obj::HERO)
+		updateAppearance();
 }
 }
 
 
 void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID)
 void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID)

+ 8 - 2
lib/spells/CSpellHandler.cpp

@@ -544,7 +544,8 @@ void CSpell::serializeJson(JsonSerializeFormat & handler)
 ///CSpell::AnimationInfo
 ///CSpell::AnimationInfo
 CSpell::AnimationItem::AnimationItem() :
 CSpell::AnimationItem::AnimationItem() :
 	verticalPosition(VerticalPosition::TOP),
 	verticalPosition(VerticalPosition::TOP),
-	pause(0)
+	pause(0),
+	transparency(1)
 {
 {
 
 
 }
 }
@@ -965,10 +966,15 @@ std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, c
 				auto vPosStr = item["verticalPosition"].String();
 				auto vPosStr = item["verticalPosition"].String();
 				if("bottom" == vPosStr)
 				if("bottom" == vPosStr)
 					newItem.verticalPosition = VerticalPosition::BOTTOM;
 					newItem.verticalPosition = VerticalPosition::BOTTOM;
+
+				if (item["transparency"].isNumber())
+					newItem.transparency = item["transparency"].Float();
+				else
+					newItem.transparency = 1.0;
 			}
 			}
 			else if(item.isNumber())
 			else if(item.isNumber())
 			{
 			{
-				newItem.pause = static_cast<int>(item.Float());
+				newItem.pause = item.Integer();
 			}
 			}
 
 
 			q.push_back(newItem);
 			q.push_back(newItem);

+ 1 - 0
lib/spells/CSpellHandler.h

@@ -74,6 +74,7 @@ public:
 		AnimationPath resourceName;
 		AnimationPath resourceName;
 		std::string effectName;
 		std::string effectName;
 		VerticalPosition verticalPosition;
 		VerticalPosition verticalPosition;
+		float transparency;
 		int pause;
 		int pause;
 
 
 		AnimationItem();
 		AnimationItem();

+ 7 - 6
server/CGameHandler.cpp

@@ -1861,12 +1861,8 @@ bool CGameHandler::bulkSmartSplitStack(SlotID slotSrc, ObjectInstanceID srcOwner
 
 
 bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player)
 bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player)
 {
 {
-	const CArmedInstance * s1 = static_cast<const CArmedInstance *>(getObjInstance(id1));
-	const CArmedInstance * s2 = static_cast<const CArmedInstance *>(getObjInstance(id2));
-	const CCreatureSet & S1 = *s1;
-	const CCreatureSet & S2 = *s2;
-	StackLocation sl1(s1, p1);
-	StackLocation sl2(s2, p2);
+	const CArmedInstance * s1 = static_cast<const CArmedInstance *>(getObj(id1));
+	const CArmedInstance * s2 = static_cast<const CArmedInstance *>(getObj(id2));
 
 
 	if (s1 == nullptr || s2 == nullptr)
 	if (s1 == nullptr || s2 == nullptr)
 	{
 	{
@@ -1874,6 +1870,11 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8
 		return false;
 		return false;
 	}
 	}
 
 
+	const CCreatureSet & S1 = *s1;
+	const CCreatureSet & S2 = *s2;
+	StackLocation sl1(s1, p1);
+	StackLocation sl2(s2, p2);
+
 	if (!sl1.slot.validSlot()  ||  !sl2.slot.validSlot())
 	if (!sl1.slot.validSlot()  ||  !sl2.slot.validSlot())
 	{
 	{
 		complain(complainInvalidSlot);
 		complain(complainInvalidSlot);