diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index f3c7e88e5..883c05922 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -759,6 +759,7 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector BATTLE_EVENT_POSSIBLE_RETURN; std::vector> newObstacles; + std::vector removedObstacles; for(auto & change : obstacles) { @@ -770,11 +771,16 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector else logNetwork->error("Invalid obstacle instance %d", change.id); } + if(change.operation == BattleChanges::EOperation::REMOVE) + removedObstacles.push_back(change); //Obstacles are already removed, so, show animation based on json struct } if (!newObstacles.empty()) battleInt->obstaclePlaced(newObstacles); + if (!removedObstacles.empty()) + battleInt->obstacleRemoved(removedObstacles); + battleInt->fieldController->redrawBackgroundWithHexes(); } diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index f61d9214c..b1bc9ca93 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -653,6 +653,11 @@ void BattleInterface::obstaclePlaced(const std::vectorobstaclePlaced(oi); } +void BattleInterface::obstacleRemoved(const std::vector & obstacles) +{ + obstacleController->obstacleRemoved(obstacles); +} + const CGHeroInstance *BattleInterface::currentHero() const { if (attackingHeroInstance && attackingHeroInstance->tempOwner == curInt->playerID) diff --git a/client/battle/BattleInterface.h b/client/battle/BattleInterface.h index 0ff9a9868..a1abfb73e 100644 --- a/client/battle/BattleInterface.h +++ b/client/battle/BattleInterface.h @@ -29,6 +29,7 @@ struct CatapultAttack; struct BattleTriggerEffect; struct BattleHex; struct InfoAboutHero; +class ObstacleChanges; VCMI_LIB_NAMESPACE_END @@ -214,6 +215,7 @@ public: void endAction(const BattleAction* action); void obstaclePlaced(const std::vector> oi); + void obstacleRemoved(const std::vector & obstacles); void gateStateChanged(const EGateState state); diff --git a/client/battle/BattleObstacleController.cpp b/client/battle/BattleObstacleController.cpp index d6d5d6bb0..911e413d6 100644 --- a/client/battle/BattleObstacleController.cpp +++ b/client/battle/BattleObstacleController.cpp @@ -74,6 +74,37 @@ void BattleObstacleController::loadObstacleImage(const CObstacleInstance & oi) obstacleAnimations[oi.uniqueID] = animationsCache[animationName]; } +void BattleObstacleController::obstacleRemoved(const std::vector & obstacles) +{ + for (auto const & oi : obstacles) + { + auto & obstacle = oi.data["obstacle"]; + + if (!obstacle.isStruct()) + { + logGlobal->error("I don't know how to animate removal of this obstacle"); + continue; + } + + auto animation = std::make_shared(obstacle["appearAnimation"].String()); + animation->preload(); + + auto first = animation->getImage(0, 0); + if(!first) + continue; + + //we assume here that effect graphics have the same size as the usual obstacle image + // -> if we know how to blit obstacle, let's blit the effect in the same place + Point whereTo = getObstaclePosition(first, obstacle); + //AFAIK, in H3 there is no sound of obstacle removal + owner.stacksController->addNewAnim(new EffectAnimation(owner, obstacle["appearAnimation"].String(), whereTo, obstacle["position"].Integer(), 0, true)); + + obstacleAnimations.erase(oi.id); + //so when multiple obstacles are removed, they show up one after another + owner.waitForAnimations(); + } +} + void BattleObstacleController::obstaclePlaced(const std::vector> & obstacles) { for (auto const & oi : obstacles) @@ -87,7 +118,7 @@ void BattleObstacleController::obstaclePlaced(const std::vectorerror("I don't know how to animate appearing obstacle of type %d", (int)oi->obstacleType); + logGlobal->error("I don't know how to animate removal of obstacle of type %d", (int)oi->obstacleType); continue; } @@ -180,3 +211,19 @@ Point BattleObstacleController::getObstaclePosition(std::shared_ptr imag return r.topLeft(); } + +Point BattleObstacleController::getObstaclePosition(std::shared_ptr image, const JsonNode & obstacle) +{ + auto animationYOffset = obstacle["animationYOffset"].Integer(); + auto offset = image->height() % 42; + + if(obstacle["needAnimationOffsetFix"].Bool() && offset > 37) + animationYOffset -= 42; + + offset += animationYOffset; + + Rect r = owner.fieldController->hexPositionLocal(obstacle["position"].Integer()); + r.y += 42 - image->height() + offset; + + return r.topLeft(); +} diff --git a/client/battle/BattleObstacleController.h b/client/battle/BattleObstacleController.h index 24c0cad02..3c3ee9ff2 100644 --- a/client/battle/BattleObstacleController.h +++ b/client/battle/BattleObstacleController.h @@ -13,6 +13,8 @@ VCMI_LIB_NAMESPACE_BEGIN struct BattleHex; struct CObstacleInstance; +class JsonNode; +class ObstacleChanges; class Point; VCMI_LIB_NAMESPACE_END @@ -42,6 +44,7 @@ class BattleObstacleController std::shared_ptr getObstacleImage(const CObstacleInstance & oi); Point getObstaclePosition(std::shared_ptr image, const CObstacleInstance & obstacle); + Point getObstaclePosition(std::shared_ptr image, const JsonNode & obstacle); public: BattleObstacleController(BattleInterface & owner); @@ -52,6 +55,9 @@ public: /// call-in from network pack, add newly placed obstacles with any required animations void obstaclePlaced(const std::vector> & oi); + /// call-in from network pack, remove required obstacles with any required animations + void obstacleRemoved(const std::vector & obstacles); + /// renders all "absolute" obstacles void showAbsoluteObstacles(Canvas & canvas); diff --git a/lib/battle/CObstacleInstance.cpp b/lib/battle/CObstacleInstance.cpp index 6b04beb9c..2dbca8747 100644 --- a/lib/battle/CObstacleInstance.cpp +++ b/lib/battle/CObstacleInstance.cpp @@ -86,6 +86,37 @@ bool CObstacleInstance::triggersEffects() const return false; } +void CObstacleInstance::serializeJson(JsonSerializeFormat & handler) +{ + auto obstacleInfo = getInfo(); + auto hidden = false; + auto needAnimationOffsetFix = obstacleType == CObstacleInstance::USUAL; + int animationYOffset = 0; + + if(getInfo().blockedTiles.front() < 0) //TODO: holy ground ID=62,65,63 + animationYOffset -= 42; + + //We need only a subset of obstacle info for correct render + handler.serializeInt("position", pos); + handler.serializeString("appearSound", obstacleInfo.appearSound); + handler.serializeString("appearAnimation", obstacleInfo.appearAnimation); + handler.serializeString("animation", obstacleInfo.animation); + handler.serializeInt("animationYOffset", animationYOffset); + + handler.serializeBool("hidden", hidden); + handler.serializeBool("needAnimationOffsetFix", needAnimationOffsetFix); +} + +void CObstacleInstance::toInfo(ObstacleChanges & info, BattleChanges::EOperation operation) +{ + info.id = uniqueID; + info.operation = operation; + + info.data.clear(); + JsonSerializer ser(nullptr, info.data); + ser.serializeStruct("obstacle", *this); +} + SpellCreatedObstacle::SpellCreatedObstacle() : turnsRemaining(-1), casterSpellPower(0), @@ -131,16 +162,6 @@ bool SpellCreatedObstacle::triggersEffects() const return trigger; } -void SpellCreatedObstacle::toInfo(ObstacleChanges & info) -{ - info.id = uniqueID; - info.operation = ObstacleChanges::EOperation::ADD; - - info.data.clear(); - JsonSerializer ser(nullptr, info.data); - ser.serializeStruct("obstacle", *this); -} - void SpellCreatedObstacle::fromInfo(const ObstacleChanges & info) { uniqueID = info.id; diff --git a/lib/battle/CObstacleInstance.h b/lib/battle/CObstacleInstance.h index 018b5b552..aaa33e80a 100644 --- a/lib/battle/CObstacleInstance.h +++ b/lib/battle/CObstacleInstance.h @@ -9,6 +9,7 @@ */ #pragma once #include "BattleHex.h" +#include "NetPacksBase.h" VCMI_LIB_NAMESPACE_BEGIN @@ -49,6 +50,10 @@ struct DLL_LINKAGE CObstacleInstance virtual int getAnimationYOffset(int imageHeight) const; + void toInfo(ObstacleChanges & info, BattleChanges::EOperation operation = BattleChanges::EOperation::ADD); + + virtual void serializeJson(JsonSerializeFormat & handler); + template void serialize(Handler &h, const int version) { h & ID; @@ -96,10 +101,9 @@ struct DLL_LINKAGE SpellCreatedObstacle : CObstacleInstance int getAnimationYOffset(int imageHeight) const override; - void toInfo(ObstacleChanges & info); void fromInfo(const ObstacleChanges & info); - void serializeJson(JsonSerializeFormat & handler); + void serializeJson(JsonSerializeFormat & handler) override; template void serialize(Handler &h, const int version) { diff --git a/lib/spells/effects/RemoveObstacle.cpp b/lib/spells/effects/RemoveObstacle.cpp index c268fd8c8..096de59c0 100644 --- a/lib/spells/effects/RemoveObstacle.cpp +++ b/lib/spells/effects/RemoveObstacle.cpp @@ -50,7 +50,11 @@ void RemoveObstacle::apply(ServerCallback * server, const Mechanics * m, const E BattleObstaclesChanged pack; for(const auto & obstacle : getTargets(m, target, false)) + { + auto * serializable = const_cast(obstacle); //Workaround pack.changes.emplace_back(obstacle->uniqueID, BattleChanges::EOperation::REMOVE); + serializable->toInfo(pack.changes.back(), BattleChanges::EOperation::REMOVE); + } if(!pack.changes.empty()) server->apply(&pack);