1
0
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:
Ivan Savenko 2024-11-18 13:29:05 +02:00 committed by GitHub
commit f0a71c9e21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 275 additions and 258 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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