mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-22 22:13:35 +02:00
Merge pull request #4935 from IvanSavenko/misc_fixes2
Miscellaneous fixes for reported issues
This commit is contained in:
commit
f0a71c9e21
@ -763,7 +763,7 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
|
||||
//you can't request action from action-response thread
|
||||
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);
|
||||
}
|
||||
|
@ -731,7 +731,7 @@ void VCAI::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance *
|
||||
//you can't request action from action-response thread
|
||||
requestActionASAP([=]()
|
||||
{
|
||||
if(removableUnits && !cb->getStartInfo()->isSteadwickFallCampaignMission())
|
||||
if(removableUnits && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
|
||||
pickBestCreatures(down, up);
|
||||
|
||||
answerQuery(queryID, 0);
|
||||
|
@ -394,7 +394,7 @@ void ClientCommandManager::handleDef2bmpCommand(std::istringstream& singleWordBu
|
||||
{
|
||||
std::string 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());
|
||||
}
|
||||
|
||||
|
@ -881,9 +881,10 @@ uint32_t CastAnimation::getAttackClimaxFrame() const
|
||||
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),
|
||||
animation(GH.renderHandler().loadAnimation(animationName, EImageBlitMode::ALPHA)),
|
||||
animation(GH.renderHandler().loadAnimation(animationName, EImageBlitMode::SIMPLE)),
|
||||
transparencyFactor(transparencyFactor),
|
||||
effectFlags(effects),
|
||||
effectFinished(false),
|
||||
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(owner, animationName, effects, reversed)
|
||||
EffectAnimation(owner, animationName, effects, 1.0f, reversed)
|
||||
{
|
||||
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());
|
||||
battlehexes.push_back(hex);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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());
|
||||
battlehexes.push_back(hex);
|
||||
@ -951,6 +952,7 @@ bool EffectAnimation::init()
|
||||
be.effectID = ID;
|
||||
be.animation = animation;
|
||||
be.currentFrame = 0;
|
||||
be.transparencyFactor = transparencyFactor;
|
||||
be.type = reversed ? BattleEffect::AnimType::REVERSE : BattleEffect::AnimType::DEFAULT;
|
||||
|
||||
for (size_t i = 0; i < std::max(battlehexes.size(), positions.size()); ++i)
|
||||
|
@ -309,9 +309,10 @@ public:
|
||||
class EffectAnimation : public BattleAnimation
|
||||
{
|
||||
std::string soundName;
|
||||
int effectFlags;
|
||||
float transparencyFactor;
|
||||
bool effectFinished;
|
||||
bool reversed;
|
||||
int effectFlags;
|
||||
|
||||
std::shared_ptr<CAnimation> animation;
|
||||
std::vector<Point> positions;
|
||||
@ -335,14 +336,14 @@ public:
|
||||
};
|
||||
|
||||
/// 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
|
||||
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);
|
||||
|
||||
/// 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, Point pos, BattleHex hex, int effects = 0, bool reversed = false);
|
||||
|
@ -44,7 +44,7 @@ void BattleEffectsController::displayEffect(EBattleEffect effect, const BattleHe
|
||||
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);
|
||||
|
||||
@ -52,7 +52,7 @@ void BattleEffectsController::displayEffect(EBattleEffect effect, const AudioPat
|
||||
|
||||
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)
|
||||
@ -69,7 +69,7 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
|
||||
switch(static_cast<BonusType>(bte.effect))
|
||||
{
|
||||
case BonusType::HP_REGENERATION:
|
||||
displayEffect(EBattleEffect::REGENERATION, AudioPath::builtin("REGENER"), stack->getPosition());
|
||||
displayEffect(EBattleEffect::REGENERATION, AudioPath::builtin("REGENER"), stack->getPosition(), 0.5);
|
||||
break;
|
||||
case BonusType::MANA_DRAIN:
|
||||
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());
|
||||
break;
|
||||
case BonusType::FEAR:
|
||||
displayEffect(EBattleEffect::FEAR, AudioPath::builtin("FEAR"), stack->getPosition());
|
||||
displayEffect(EBattleEffect::FEAR, AudioPath::builtin("FEAR"), stack->getPosition(), 0.5);
|
||||
break;
|
||||
case BonusType::MORALE:
|
||||
{
|
||||
@ -124,6 +124,7 @@ void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer
|
||||
currentFrame %= elem.animation->size();
|
||||
|
||||
auto img = elem.animation->getImage(currentFrame, static_cast<size_t>(elem.type));
|
||||
img->setAlpha(255 * elem.transparencyFactor);
|
||||
|
||||
canvas.draw(img, elem.pos);
|
||||
});
|
||||
|
@ -39,7 +39,8 @@ struct BattleEffect
|
||||
|
||||
AnimType type;
|
||||
Point pos; //position on the screen
|
||||
float currentFrame;
|
||||
float currentFrame = 0.0;
|
||||
float transparencyFactor = 1.0;
|
||||
std::shared_ptr<CAnimation> animation;
|
||||
int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim
|
||||
BattleHex tile; //Indicates if effect which hex the effect is drawn on
|
||||
@ -65,7 +66,7 @@ public:
|
||||
|
||||
//displays custom effect on the battlefield
|
||||
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);
|
||||
|
||||
|
@ -114,7 +114,7 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
|
||||
|
||||
//preparing cells and hexes
|
||||
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);
|
||||
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);
|
||||
shootingRangeLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsRed.json"), EImageBlitMode::COLORKEY);
|
||||
|
||||
cellShade->setShadowEnabled(true);
|
||||
|
||||
if(!owner.siegeController)
|
||||
{
|
||||
auto bfieldType = owner.getBattle()->battleGetBattlefieldType();
|
||||
|
@ -535,9 +535,9 @@ void BattleInterface::displaySpellAnimationQueue(const CSpell * spell, const CSp
|
||||
flags |= EffectAnimation::SCREEN_FILL;
|
||||
|
||||
if (!destinationTile.isValid())
|
||||
stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, flags));
|
||||
stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, flags, animation.transparency));
|
||||
else
|
||||
stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, destinationTile, flags));
|
||||
stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, destinationTile, flags, animation.transparency));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -398,7 +398,7 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
|
||||
else
|
||||
animationPath = hero->getHeroClass()->imageBattleMale;
|
||||
|
||||
animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::ALPHA);
|
||||
animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::WITH_SHADOW);
|
||||
|
||||
pos.w = 64;
|
||||
pos.h = 136;
|
||||
|
@ -50,11 +50,11 @@ void BattleObstacleController::loadObstacleImage(const CObstacleInstance & oi)
|
||||
if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -78,7 +78,7 @@ void BattleObstacleController::obstacleRemoved(const std::vector<ObstacleChanges
|
||||
if(animationPath.empty())
|
||||
continue;
|
||||
|
||||
auto animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::COLORKEY);
|
||||
auto animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::SIMPLE);
|
||||
auto first = animation->getImage(0, 0);
|
||||
if(!first)
|
||||
continue;
|
||||
@ -105,7 +105,7 @@ void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<
|
||||
if(!oi->visibleForSide(side, owner.getBattle()->battleHasNativeStack(side)))
|
||||
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);
|
||||
if(!first)
|
||||
continue;
|
||||
|
@ -636,7 +636,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "../render/CAnimation.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/ColorFilter.h"
|
||||
#include "../render/Colors.h"
|
||||
#include "../render/IRenderHandler.h"
|
||||
|
||||
static const ColorRGBA creatureBlueBorder = { 0, 255, 255, 255 };
|
||||
@ -199,8 +200,8 @@ CreatureAnimation::CreatureAnimation(const AnimationPath & name_, TSpeedControll
|
||||
speedController(controller),
|
||||
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(forward->size(size_t(ECreatureAnimType::DEAD)) == 0)
|
||||
@ -339,15 +340,14 @@ void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter,
|
||||
|
||||
if(image)
|
||||
{
|
||||
image->setShadowEnabled(true);
|
||||
image->setOverlayEnabled(isIdle());
|
||||
if (isIdle())
|
||||
image->setOverlayColor(genBorderColor(getBorderStrength(elapsedTime), border));
|
||||
else
|
||||
image->setOverlayColor(Colors::TRANSPARENCY);
|
||||
|
||||
image->adjustPalette(shifter, 0);
|
||||
|
||||
canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,7 +316,7 @@ uint8_t MapRendererBorder::checksum(IMapRendererContext & context, const int3 &
|
||||
MapRendererFow::MapRendererFow()
|
||||
{
|
||||
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};
|
||||
|
||||
@ -383,24 +383,25 @@ std::shared_ptr<CAnimation> MapRendererObjects::getBaseAnimation(const CGObjectI
|
||||
}
|
||||
|
||||
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
|
||||
// proper boat animations are actually in different file
|
||||
if (info->id == Obj::BOAT)
|
||||
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);
|
||||
|
||||
if(it != animations.end())
|
||||
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;
|
||||
|
||||
if(generateMovementGroups)
|
||||
@ -427,14 +428,14 @@ std::shared_ptr<CAnimation> MapRendererObjects::getFlagAnimation(const CGObjectI
|
||||
{
|
||||
assert(dynamic_cast<const CGHeroInstance *>(obj) != nullptr);
|
||||
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)
|
||||
{
|
||||
const auto * boat = dynamic_cast<const CGBoat *>(obj);
|
||||
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;
|
||||
@ -447,7 +448,7 @@ std::shared_ptr<CAnimation> MapRendererObjects::getOverlayAnimation(const CGObje
|
||||
// Boats have additional animation with waves around boat
|
||||
const auto * boat = dynamic_cast<const CGBoat *>(obj);
|
||||
if(boat && boat->hero && !boat->overlayAnimation.empty())
|
||||
return getAnimation(boat->overlayAnimation, true);
|
||||
return getAnimation(boat->overlayAnimation, true, false);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@ -478,22 +479,14 @@ void MapRendererObjects::renderImage(IMapRendererContext & context, Canvas & tar
|
||||
return;
|
||||
|
||||
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())
|
||||
image->setOverlayColor(graphics->playerColors[object->getOwner().getNum()]);
|
||||
|
||||
if (object->getOwner() == PlayerColor::NEUTRAL)
|
||||
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);
|
||||
|
||||
@ -567,10 +560,10 @@ uint8_t MapRendererObjects::checksum(IMapRendererContext & context, const int3 &
|
||||
}
|
||||
|
||||
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()
|
||||
: pathNodes(GH.renderHandler().loadAnimation(AnimationPath::builtin("ADAG"), EImageBlitMode::ALPHA))
|
||||
: pathNodes(GH.renderHandler().loadAnimation(AnimationPath::builtin("ADAG"), EImageBlitMode::SIMPLE))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ class MapRendererObjects
|
||||
std::shared_ptr<CAnimation> getFlagAnimation(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;
|
||||
|
||||
|
@ -37,9 +37,29 @@ enum class EImageBlitMode : uint8_t
|
||||
/// RGBA: full alpha transparency range, e.g. shadows
|
||||
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.
|
||||
@ -75,9 +95,6 @@ public:
|
||||
//only indexed bitmaps with 7 special colors
|
||||
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 ~IImage() = default;
|
||||
|
@ -124,8 +124,12 @@ std::string ImageLocator::toString() const
|
||||
if (playerColored.isValidPlayer())
|
||||
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;
|
||||
}
|
||||
|
@ -9,18 +9,11 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "IImage.h"
|
||||
|
||||
#include "../../lib/filesystem/ResourcePath.h"
|
||||
#include "../../lib/constants/EntityIdentifiers.h"
|
||||
|
||||
enum class EImageLayer
|
||||
{
|
||||
ALL,
|
||||
|
||||
BODY,
|
||||
SHADOW,
|
||||
OVERLAY,
|
||||
};
|
||||
|
||||
struct ImageLocator
|
||||
{
|
||||
std::optional<ImagePath> image;
|
||||
@ -28,13 +21,13 @@ struct ImageLocator
|
||||
int defFrame = -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 horizontalFlip = false;
|
||||
int8_t scalingFactor = 0; // 0 = auto / use default scaling
|
||||
int8_t preScaledFactor = 1;
|
||||
EImageLayer layer = EImageLayer::ALL;
|
||||
EImageBlitMode layer = EImageBlitMode::OPAQUE;
|
||||
|
||||
ImageLocator() = default;
|
||||
ImageLocator(const AnimationPath & path, int frame, int group);
|
||||
|
@ -28,9 +28,7 @@ ImageScaled::ImageScaled(const ImageLocator & inputLocator, const std::shared_pt
|
||||
, alphaValue(SDL_ALPHA_OPAQUE)
|
||||
, blitMode(mode)
|
||||
{
|
||||
setBodyEnabled(true);
|
||||
if (mode == EImageBlitMode::ALPHA)
|
||||
setShadowEnabled(true);
|
||||
prepareImages();
|
||||
}
|
||||
|
||||
std::shared_ptr<const ISharedImage> ImageScaled::getSharedImage() const
|
||||
@ -92,8 +90,7 @@ void ImageScaled::setOverlayColor(const ColorRGBA & color)
|
||||
void ImageScaled::playerColored(PlayerColor player)
|
||||
{
|
||||
playerColor = player;
|
||||
if (body)
|
||||
setBodyEnabled(true); // regenerate
|
||||
prepareImages();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
else
|
||||
shadow = nullptr;
|
||||
}
|
||||
case EImageBlitMode::OPAQUE:
|
||||
case EImageBlitMode::COLORKEY:
|
||||
case EImageBlitMode::SIMPLE:
|
||||
locator.layer = blitMode;
|
||||
locator.playerColored = playerColor;
|
||||
body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage();
|
||||
break;
|
||||
|
||||
void ImageScaled::setBodyEnabled(bool on)
|
||||
{
|
||||
if (on)
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ private:
|
||||
uint8_t alphaValue;
|
||||
EImageBlitMode blitMode;
|
||||
|
||||
void prepareImages();
|
||||
public:
|
||||
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 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;
|
||||
};
|
||||
|
@ -303,22 +303,14 @@ std::shared_ptr<const ISharedImage> RenderHandler::scaleImage(const ImageLocator
|
||||
if (imageFiles.count(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
|
||||
|
||||
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->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();
|
||||
storeCachedImage(locator, result);
|
||||
return result;
|
||||
@ -331,9 +323,9 @@ std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator, E
|
||||
if(adjustedLocator.image)
|
||||
{
|
||||
std::string imgPath = (*adjustedLocator.image).getName();
|
||||
if(adjustedLocator.layer == EImageLayer::OVERLAY)
|
||||
if(adjustedLocator.layer == EImageBlitMode::ONLY_OVERLAY)
|
||||
imgPath += "-OVERLAY";
|
||||
if(adjustedLocator.layer == EImageLayer::SHADOW)
|
||||
if(adjustedLocator.layer == EImageBlitMode::ONLY_SHADOW)
|
||||
imgPath += "-SHADOW";
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
@ -139,6 +139,8 @@ SDLImageShared::SDLImageShared(const ImagePath & filename, int preScaleFactor)
|
||||
savePalette();
|
||||
fullSize.x = surf->w;
|
||||
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)
|
||||
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);
|
||||
}
|
||||
@ -261,6 +263,13 @@ void SDLImageShared::optimizeSurface()
|
||||
SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
|
||||
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);
|
||||
surf = newSurface;
|
||||
|
||||
@ -357,7 +366,7 @@ void SDLImageIndexed::playerColored(PlayerColor player)
|
||||
bool SDLImageShared::isTransparent(const Point & coords) const
|
||||
{
|
||||
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
|
||||
return true;
|
||||
}
|
||||
@ -425,7 +434,7 @@ void SDLImageIndexed::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove,
|
||||
void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
|
||||
{
|
||||
// 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);
|
||||
|
||||
// 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)
|
||||
,originalPalette(originalPalette)
|
||||
{
|
||||
|
||||
currentPalette = SDL_AllocPalette(originalPalette->ncolors);
|
||||
SDL_SetPaletteColors(currentPalette, originalPalette->colors, 0, originalPalette->ncolors);
|
||||
|
||||
if (mode == EImageBlitMode::ALPHA)
|
||||
{
|
||||
setOverlayColor(Colors::TRANSPARENCY);
|
||||
setShadowTransparency(1.0);
|
||||
}
|
||||
preparePalette();
|
||||
}
|
||||
|
||||
SDLImageIndexed::~SDLImageIndexed()
|
||||
@ -500,36 +504,42 @@ void SDLImageIndexed::setOverlayColor(const ColorRGBA & color)
|
||||
}
|
||||
}
|
||||
|
||||
void SDLImageIndexed::setShadowEnabled(bool on)
|
||||
void SDLImageIndexed::preparePalette()
|
||||
{
|
||||
if (on)
|
||||
setShadowTransparency(1.0);
|
||||
switch(blitMode)
|
||||
{
|
||||
case EImageBlitMode::ONLY_SHADOW:
|
||||
case EImageBlitMode::ONLY_OVERLAY:
|
||||
adjustPalette(ColorFilter::genAlphaShifter(0), 0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!on && blitMode == EImageBlitMode::ALPHA)
|
||||
setShadowTransparency(0.0);
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (on)
|
||||
setOverlayColor(Colors::WHITE_TRUE);
|
||||
|
||||
if (!on && blitMode == EImageBlitMode::ALPHA)
|
||||
setOverlayColor(Colors::TRANSPARENCY);
|
||||
|
||||
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()
|
||||
@ -609,21 +619,6 @@ void SDLImageBase::setBlitMode(EImageBlitMode 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)
|
||||
{}
|
||||
|
||||
|
@ -89,11 +89,8 @@ class SDLImageIndexed final : public SDLImageBase
|
||||
SDL_Palette * currentPalette = nullptr;
|
||||
SDL_Palette * originalPalette = nullptr;
|
||||
|
||||
bool bodyEnabled = true;
|
||||
bool shadowEnabled = false;
|
||||
bool overlayEnabled = false;
|
||||
|
||||
void setShadowTransparency(float factor);
|
||||
void preparePalette();
|
||||
public:
|
||||
SDLImageIndexed(const std::shared_ptr<const ISharedImage> & image, SDL_Palette * palette, EImageBlitMode mode);
|
||||
~SDLImageIndexed();
|
||||
@ -106,10 +103,6 @@ public:
|
||||
void scaleInteger(int factor) override;
|
||||
void scaleTo(const Point & size) 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
|
||||
@ -125,8 +118,4 @@ public:
|
||||
void scaleInteger(int factor) override;
|
||||
void scaleTo(const Point & size) 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;
|
||||
};
|
||||
|
@ -90,7 +90,7 @@ SDL_Surface * CSDL_Ext::newSurface(const Point & dimensions, SDL_Surface * mod)
|
||||
if (mod->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));
|
||||
}
|
||||
return ret;
|
||||
|
@ -274,12 +274,12 @@ bool CGarrisonSlot::mustForceReselection() const
|
||||
if (!LOCPLINT->makingTurn)
|
||||
return true;
|
||||
|
||||
if (!creature || !selection->creature)
|
||||
return false;
|
||||
|
||||
// Attempt to take creatures from ally (select theirs first)
|
||||
if (!selection->our())
|
||||
return true;
|
||||
|
||||
if (!creature || !selection->creature)
|
||||
return false;
|
||||
|
||||
// Attempt to swap creatures with ally (select ours first)
|
||||
if (selection->creature != creature && withAlly)
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "../render/CAnimation.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/ColorFilter.h"
|
||||
#include "../render/Colors.h"
|
||||
|
||||
#include "../battle/BattleInterface.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.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();
|
||||
}
|
||||
|
||||
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),
|
||||
group(Group),
|
||||
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):
|
||||
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),
|
||||
frame(0),
|
||||
first(0),
|
||||
@ -430,9 +431,8 @@ void CShowableAnim::blitImage(size_t frame, size_t group, Canvas & to)
|
||||
auto img = anim->getImage(frame, group);
|
||||
if(img)
|
||||
{
|
||||
if (flags & CREATURE_MODE)
|
||||
img->setShadowEnabled(true);
|
||||
img->setAlpha(alpha);
|
||||
img->setOverlayColor(Colors::TRANSPARENCY);
|
||||
to.draw(img, pos.topLeft(), src);
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town
|
||||
border = GH.renderHandler().loadImage(str->borderName, EImageBlitMode::COLORKEY);
|
||||
|
||||
if(!str->areaName.empty())
|
||||
area = GH.renderHandler().loadImage(str->areaName, EImageBlitMode::ALPHA);
|
||||
area = GH.renderHandler().loadImage(str->areaName, EImageBlitMode::SIMPLE);
|
||||
}
|
||||
|
||||
const CBuilding * CBuildingRect::getBuilding()
|
||||
|
@ -199,10 +199,11 @@ void CHeroWindow::update()
|
||||
OBJECT_CONSTRUCTION;
|
||||
if(!garr)
|
||||
{
|
||||
bool removableTroops = curHero->getOwner() == LOCPLINT->playerID;
|
||||
std::string helpBox = heroscrn[32];
|
||||
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);
|
||||
garr->addSplitBtn(split);
|
||||
}
|
||||
|
@ -950,7 +950,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID bu
|
||||
}
|
||||
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;
|
||||
speechStr = uni->speech;
|
||||
}
|
||||
|
@ -22,7 +22,8 @@
|
||||
"properties" : {
|
||||
"verticalPosition" : {"type" : "string", "enum" :["top","bottom"]},
|
||||
"defName" : {"type" : "string", "format" : "animationFile"},
|
||||
"effectName" : { "type" : "string" }
|
||||
"effectName" : { "type" : "string" },
|
||||
"transparency" : {"type" : "number", "minimum" : 0, "maximum" : 1}
|
||||
},
|
||||
"additionalProperties" : false
|
||||
}
|
||||
|
@ -252,7 +252,7 @@
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
"hit":["SP04_"]
|
||||
"hit":[{ "defName" : "SP04_", "transparency" : 0.5}]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "DEATHCLD"
|
||||
|
@ -44,7 +44,7 @@
|
||||
{"minimumAngle": 1.20 ,"defName":"C08SPW1"},
|
||||
{"minimumAngle": 1.50 ,"defName":"C08SPW0"}
|
||||
],
|
||||
"hit":["C08SPW5"]
|
||||
"hit":[ {"defName" : "C08SPW5", "transparency" : 0.5 }]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "ICERAY"
|
||||
@ -309,7 +309,7 @@
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C14SPA0"]
|
||||
"affect":[{"defName" : "C14SPA0", "transparency" : 0.5}]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SACBRETH"
|
||||
|
@ -483,7 +483,7 @@
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C01SPE0"]
|
||||
"affect":[{ "defName" : "C01SPE0", "transparency" : 0.5}]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "RESURECT"
|
||||
|
@ -652,7 +652,7 @@
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":["C07SPA1"],
|
||||
"affect":[{"defName" : "C07SPA1", "transparency" : 0.5}],
|
||||
"projectile":[{"defName":"C07SPA0"}]//???
|
||||
},
|
||||
"sounds": {
|
||||
@ -696,7 +696,7 @@
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
"animation":{
|
||||
"affect":[{"defName":"C10SPW", "verticalPosition":"bottom"}]
|
||||
"affect":[{"defName":"C10SPW", "verticalPosition":"bottom", "transparency" : 0.5}]
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "PRAYER"
|
||||
|
@ -167,7 +167,7 @@ TODO
|
||||
],
|
||||
"cast" : []
|
||||
"hit":["C20SPX"],
|
||||
"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
|
||||
"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom", "transparency" : 0.5}, "C11SPA1"]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
# 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
|
||||
|
||||
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.
|
||||
|
||||
@ -20,10 +20,15 @@ The sprites should have the same name and folder structure as in `sprites` and `
|
||||
|
||||
### 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.
|
||||
|
||||
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)
|
||||
|
@ -957,12 +957,12 @@ void CGameInfoCallback::calculatePaths( const CGHeroInstance *hero, CPathsInfo &
|
||||
|
||||
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
|
||||
{
|
||||
return gs->map->objects[oid.num];
|
||||
return gs->map->objects.at(oid.num);
|
||||
}
|
||||
|
||||
const CArtifactSet * CGameInfoCallback::getArtSet(const ArtifactLocation & loc) const
|
||||
|
@ -90,18 +90,22 @@ std::string StartInfo::getCampaignName() const
|
||||
return VLC->generaltexth->allTexts[508];
|
||||
}
|
||||
|
||||
bool StartInfo::isSteadwickFallCampaignMission() const
|
||||
bool StartInfo::isRestorationOfErathiaCampaign() const
|
||||
{
|
||||
constexpr std::array roeCampaigns = {
|
||||
"DATA/GOOD1",
|
||||
"DATA/EVIL1",
|
||||
"DATA/GOOD2",
|
||||
"DATA/NEUTRAL1",
|
||||
"DATA/EVIL2",
|
||||
"DATA/GOOD3",
|
||||
"DATA/SECRET1",
|
||||
};
|
||||
|
||||
if (!campState)
|
||||
return false;
|
||||
|
||||
if (campState->getFilename() != "DATA/EVIL1")
|
||||
return false;
|
||||
|
||||
if (campState->currentScenario() != CampaignScenarioID(2))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return vstd::contains(roeCampaigns, campState->getFilename());
|
||||
}
|
||||
|
||||
void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const
|
||||
|
@ -164,8 +164,8 @@ struct DLL_LINKAGE StartInfo : public Serializeable
|
||||
// TODO: Must be client-side
|
||||
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>
|
||||
void serialize(Handler &h)
|
||||
|
@ -23,29 +23,20 @@ SecondarySkill CHeroClass::chooseSecSkill(const std::set<SecondarySkill> & possi
|
||||
{
|
||||
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)
|
||||
{
|
||||
skills.push_back(possible);
|
||||
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
|
||||
|
@ -88,25 +88,28 @@ void DwellingInstanceConstructor::randomizeObject(CGDwelling * dwelling, vstd::R
|
||||
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())
|
||||
{
|
||||
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;
|
||||
for(auto & stack : randomizer.loadCreatures(guards, rng, emptyVariables))
|
||||
{
|
||||
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)
|
||||
{
|
||||
if(creatureEntry.at(0)->getLevel() >= 5)
|
||||
|
@ -338,7 +338,8 @@ void CGHeroInstance::setHeroType(HeroTypeID heroType)
|
||||
|
||||
void CGHeroInstance::initObj(vstd::RNG & rand)
|
||||
{
|
||||
updateAppearance();
|
||||
if (ID == Obj::HERO)
|
||||
updateAppearance();
|
||||
}
|
||||
|
||||
void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID)
|
||||
|
@ -544,7 +544,8 @@ void CSpell::serializeJson(JsonSerializeFormat & handler)
|
||||
///CSpell::AnimationInfo
|
||||
CSpell::AnimationItem::AnimationItem() :
|
||||
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();
|
||||
if("bottom" == vPosStr)
|
||||
newItem.verticalPosition = VerticalPosition::BOTTOM;
|
||||
|
||||
if (item["transparency"].isNumber())
|
||||
newItem.transparency = item["transparency"].Float();
|
||||
else
|
||||
newItem.transparency = 1.0;
|
||||
}
|
||||
else if(item.isNumber())
|
||||
{
|
||||
newItem.pause = static_cast<int>(item.Float());
|
||||
newItem.pause = item.Integer();
|
||||
}
|
||||
|
||||
q.push_back(newItem);
|
||||
|
@ -74,6 +74,7 @@ public:
|
||||
AnimationPath resourceName;
|
||||
std::string effectName;
|
||||
VerticalPosition verticalPosition;
|
||||
float transparency;
|
||||
int pause;
|
||||
|
||||
AnimationItem();
|
||||
|
@ -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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -1874,6 +1870,11 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8
|
||||
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())
|
||||
{
|
||||
complain(complainInvalidSlot);
|
||||
|
Loading…
Reference in New Issue
Block a user