diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 412215deb..bfa1d4c15 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -533,7 +533,7 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas) BattleHexArray hoveredMoveHexes = getHighlightedHexesForMovementTarget(); BattleHex hoveredHex = getHoveredHex(); - BattleHexArray hoveredMouseHex = hoveredHex.isValid() ? BattleHexArray({ hoveredHex }) : BattleHexArray(); + BattleHexArray hoveredMouseHex = hoveredHex.isAvailable() ? BattleHexArray({ hoveredHex }) : BattleHexArray(); const CStack * hoveredStack = getHoveredStack(); if(!hoveredStack && hoveredHex == BattleHex::INVALID) diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 1f57228da..5240737d5 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -691,7 +691,7 @@ void StackInfoBasicPanel::initializeData(const CStack * stack) if (spellBonuses->empty()) throw std::runtime_error("Failed to find effects for spell " + effect.toSpell()->getJsonKey()); - int duration = spellBonuses->front()->duration; + int duration = spellBonuses->front()->turnsRemain; icons.push_back(std::make_shared(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed)); if(settings["general"]["enableUiEnhancements"].Bool()) diff --git a/client/globalLobby/GlobalLobbyWidget.cpp b/client/globalLobby/GlobalLobbyWidget.cpp index da3adf3be..46ced84d4 100644 --- a/client/globalLobby/GlobalLobbyWidget.cpp +++ b/client/globalLobby/GlobalLobbyWidget.cpp @@ -28,6 +28,7 @@ #include "../widgets/Images.h" #include "../widgets/MiscWidgets.h" #include "../widgets/ObjectLists.h" +#include "../widgets/Slider.h" #include "../widgets/TextControls.h" #include "../../lib/CConfigHandler.h" @@ -126,6 +127,15 @@ std::shared_ptr GlobalLobbyWidget::buildItemList(const JsonNode & co auto result = std::make_shared(callback, position, itemOffset, visibleAmount, totalAmount, initialPos, sliderMode, Rect(sliderPosition, sliderSize)); + if (result->getSlider()) + { + Point scrollBoundsDimensions(sliderPosition.x + result->getSlider()->pos.w, result->getSlider()->pos.h); + Point scrollBoundsOffset = -sliderPosition; + + result->getSlider()->setScrollBounds(Rect(scrollBoundsOffset, scrollBoundsDimensions)); + result->getSlider()->setPanningStep(itemOffset.length()); + } + result->setRedrawParent(true); return result; } diff --git a/client/render/AssetGenerator.cpp b/client/render/AssetGenerator.cpp index 6f264b0ac..aacc9105e 100644 --- a/client/render/AssetGenerator.cpp +++ b/client/render/AssetGenerator.cpp @@ -247,7 +247,7 @@ AssetGenerator::CanvasPtr AssetGenerator::createCampaignBackground() AssetGenerator::CanvasPtr AssetGenerator::createChroniclesCampaignImages(int chronicle) { - auto imgPathBg = ImagePath::builtin("data/chronicles_" + std::to_string(chronicle) + "/GamSelBk"); + auto imgPathBg = ImagePath::builtin("chronicles_" + std::to_string(chronicle) + "/GamSelBk"); auto locator = ImageLocator(imgPathBg, EImageBlitMode::OPAQUE); std::shared_ptr img = GH.renderHandler().loadImage(locator); diff --git a/client/render/CanvasImage.cpp b/client/render/CanvasImage.cpp index dc6d619bb..3ed438363 100644 --- a/client/render/CanvasImage.cpp +++ b/client/render/CanvasImage.cpp @@ -25,6 +25,11 @@ CanvasImage::CanvasImage(const Point & size, CanvasScalingPolicy scalingPolicy) { } +CanvasImage::~CanvasImage() +{ + SDL_FreeSurface(surface); +} + void CanvasImage::draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const { if(src) diff --git a/client/render/CanvasImage.h b/client/render/CanvasImage.h index 095a4276b..56452c926 100644 --- a/client/render/CanvasImage.h +++ b/client/render/CanvasImage.h @@ -16,6 +16,7 @@ class CanvasImage : public IImage { public: CanvasImage(const Point & size, CanvasScalingPolicy scalingPolicy); + ~CanvasImage(); Canvas getCanvas(); diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index aa771c3e9..3e6e01630 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -379,7 +379,7 @@ void RenderHandler::addImageListEntries(const EntityService * service) if (imageName.empty()) return; - auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName), 1, EImageBlitMode::SIMPLE); + auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName), 1, EImageBlitMode::COLORKEY); JsonNode entry; entry["file"].String() = imageName; @@ -417,8 +417,8 @@ static void detectOverlappingBuildings(RenderHandler * renderHandler, const Fact if (left->pos.z != right->pos.z) continue; // buildings already have different z-index and have well-defined overlap logic - auto leftImage = renderHandler->loadImage(left->defName, 0, 0, EImageBlitMode::SIMPLE); - auto rightImage = renderHandler->loadImage(right->defName, 0, 0, EImageBlitMode::SIMPLE); + auto leftImage = renderHandler->loadImage(left->defName, 0, 0, EImageBlitMode::COLORKEY); + auto rightImage = renderHandler->loadImage(right->defName, 0, 0, EImageBlitMode::COLORKEY); Rect leftRect( left->pos.x, left->pos.y, leftImage->width(), leftImage->height()); Rect rightRect( right->pos.x, right->pos.y, rightImage->width(), rightImage->height()); diff --git a/client/widgets/ObjectLists.cpp b/client/widgets/ObjectLists.cpp index 6470df4a5..2f4b2d190 100644 --- a/client/widgets/ObjectLists.cpp +++ b/client/widgets/ObjectLists.cpp @@ -168,6 +168,11 @@ std::shared_ptr CListBox::getItem(size_t which) return std::shared_ptr(); } +std::shared_ptr CListBox::getSlider() +{ + return slider; +} + size_t CListBox::getIndexOf(std::shared_ptr item) { size_t i=first; diff --git a/client/widgets/ObjectLists.h b/client/widgets/ObjectLists.h index 11703130c..91b8b99cb 100644 --- a/client/widgets/ObjectLists.h +++ b/client/widgets/ObjectLists.h @@ -91,6 +91,8 @@ public: //return item with index which or null if not present std::shared_ptr getItem(size_t which); + std::shared_ptr getSlider(); + //return currently active items const std::list> & getItems(); diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index e86bbf864..8973c19f9 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -238,7 +238,7 @@ CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int if (spellBonuses->empty()) throw std::runtime_error("Failed to find effects for spell " + effect.toSpell()->getJsonKey()); - int duration = spellBonuses->front()->duration; + int duration = spellBonuses->front()->turnsRemain; boost::replace_first(spellText, "%d", std::to_string(duration)); spellIcons.push_back(std::make_shared(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed)); diff --git a/config/campaignSets.json b/config/campaignSets.json index ce118a7fc..607d1be05 100644 --- a/config/campaignSets.json +++ b/config/campaignSets.json @@ -50,7 +50,7 @@ }, "chr": { - "images" : [ {"x": 0, "y": 0, "name":"data/CampaignBackground8"} ], + "images" : [ {"x": 0, "y": 0, "name":"CampaignBackground8"} ], "exitbutton" : {"x": 658, "y": 482, "name":"CMPSCAN" }, "items": [ diff --git a/config/mapOverrides.json b/config/mapOverrides.json index 7d9206bf6..55a81df97 100644 --- a/config/mapOverrides.json +++ b/config/mapOverrides.json @@ -2370,5 +2370,120 @@ }, "victoryIconIndex" : 11, "victoryString" : "core.vcdesc.0" + }, + + "data/gelu:1" : { // Cutthroats + "defeatIconIndex" : 1, + "defeatString" : "core.lcdesc.2", + "triggeredEvents" : { + "heroesMustSurvive" : { + "condition" : [ + "allOf", + [ "isHuman", { "value" : 1 } ], + [ "noneOf", + [ "control", { "position" : [ 11, 8, 0 ], "type" : "hero" } ] // Gelu + ] + ], + "effect" : { + "messageToSend" : "core.genrltxt.5", + "type" : "defeat" + }, + "message" : "core.genrltxt.253" + }, + "specialVictory" : { + "condition" : [ "haveArtifact", { "type" : "artifact.ringOfVitality" } ], + "effect" : { + "messageToSend" : "core.genrltxt.281", + "type" : "victory" + }, + "message" : "core.genrltxt.280" + }, + "standardDefeat" : { + "condition" : [ "daysWithoutTown", { "value" : 7 } ], + "effect" : { + "messageToSend" : "core.genrltxt.8", + "type" : "defeat" + }, + "message" : "core.genrltxt.7" + } + }, + "victoryIconIndex" : 0, + "victoryString" : "core.vcdesc.1" + }, + "data/gelu:2" : { // Valley of the Dragon Lords + "defeatIconIndex" : 1, + "defeatString" : "core.lcdesc.2", + "triggeredEvents" : { + "heroesMustSurvive" : { + "condition" : [ + "allOf", + [ "isHuman", { "value" : 1 } ], + [ "noneOf", + [ "control", { "position" : [ 62, 12, 0 ], "type" : "hero" } ] // Gelu + ] + ], + "effect" : { + "messageToSend" : "core.genrltxt.5", + "type" : "defeat" + }, + "message" : "core.genrltxt.253" + }, + "specialVictory" : { + "condition" : [ "haveArtifact", { "type" : "artifact.ringOfLife" } ], + "effect" : { + "messageToSend" : "core.genrltxt.281", + "type" : "victory" + }, + "message" : "core.genrltxt.280" + }, + "standardDefeat" : { + "condition" : [ "daysWithoutTown", { "value" : 7 } ], + "effect" : { + "messageToSend" : "core.genrltxt.8", + "type" : "defeat" + }, + "message" : "core.genrltxt.7" + } + }, + "victoryIconIndex" : 0, + "victoryString" : "core.vcdesc.1" + }, + "data/gelu:3" : { // A Thief in the Night + "defeatIconIndex" : 1, + "defeatString" : "core.lcdesc.2", + "triggeredEvents" : { + "heroesMustSurvive" : { + "condition" : [ + "allOf", + [ "isHuman", { "value" : 1 } ], + [ "noneOf", + [ "control", { "position" : [ 50, 9, 0 ], "type" : "hero" } ] // Gelu + ] + ], + "effect" : { + "messageToSend" : "core.genrltxt.5", + "type" : "defeat" + }, + "message" : "core.genrltxt.253" + }, + "specialVictory" : { + "condition" : [ "haveArtifact", { "type" : "artifact.vialOfLifeblood" } ], + "effect" : { + "messageToSend" : "core.genrltxt.281", + "type" : "victory" + }, + "message" : "core.genrltxt.280" + }, + "standardDefeat" : { + "condition" : [ "daysWithoutTown", { "value" : 7 } ], + "effect" : { + "messageToSend" : "core.genrltxt.8", + "type" : "defeat" + }, + "message" : "core.genrltxt.7" + } + }, + "victoryIconIndex" : 0, + "victoryString" : "core.vcdesc.1" } } diff --git a/lib/bonuses/CBonusSystemNode.cpp b/lib/bonuses/CBonusSystemNode.cpp index 9f18a1cb5..e1c2d7f46 100644 --- a/lib/bonuses/CBonusSystemNode.cpp +++ b/lib/bonuses/CBonusSystemNode.cpp @@ -188,6 +188,7 @@ std::shared_ptr CBonusSystemNode::getUpdatedBonus(const std::shared_ptraddLoader(knownLoaders["saves"], true); localFS->addLoader(knownLoaders["config"], true); - localFS->addLoader(knownLoaders["gen_data"], true); - localFS->addLoader(knownLoaders["gen_sprites"], true); addFilesystem("root", "initial", createInitial()); addFilesystem("root", "data", new CFilesystemList()); diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index 66ed48dbd..62c77d610 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -117,8 +117,17 @@ void CMapHeader::setupEvents() defeatMessage.appendTextID("core.lcdesc.0"); } -CMapHeader::CMapHeader() : version(EMapFormat::VCMI), height(72), width(72), - twoLevel(true), difficulty(EMapDifficulty::NORMAL), levelLimit(0), howManyTeams(0), areAnyPlayers(false) +CMapHeader::CMapHeader() + : version(EMapFormat::VCMI) + , height(72) + , width(72) + , twoLevel(true) + , difficulty(EMapDifficulty::NORMAL) + , levelLimit(0) + , howManyTeams(0) + , areAnyPlayers(false) + , victoryIconIndex(0) + , defeatIconIndex(0) { setupEvents(); allowedHeroes = VLC->heroh->getDefaultAllowed(); diff --git a/lib/network/NetworkConnection.cpp b/lib/network/NetworkConnection.cpp index e77e683dd..3c8d7934e 100644 --- a/lib/network/NetworkConnection.cpp +++ b/lib/network/NetworkConnection.cpp @@ -216,22 +216,25 @@ InternalConnection::InternalConnection(INetworkConnectionListener & listener, co void InternalConnection::receivePacket(const std::vector & message) { - io->post([self = shared_from_this(), message](){ - self->listener.onPacketReceived(self, message); + io->post([self = std::static_pointer_cast(shared_from_this()), message](){ + if (self->connectionActive) + self->listener.onPacketReceived(self, message); }); } void InternalConnection::disconnect() { - io->post([self = shared_from_this()](){ + io->post([self = std::static_pointer_cast(shared_from_this())](){ self->listener.onDisconnected(self, "Internal connection has been terminated"); self->otherSideWeak.reset(); + self->connectionActive = false; }); } void InternalConnection::connectTo(std::shared_ptr connection) { otherSideWeak = connection; + connectionActive = true; } void InternalConnection::sendPacket(const std::vector & message) @@ -240,8 +243,6 @@ void InternalConnection::sendPacket(const std::vector & message) if (otherSide) otherSide->receivePacket(message); - else - throw std::runtime_error("Failed to send packet! Connection has been deleted!"); } void InternalConnection::setAsyncWritesEnabled(bool on) @@ -257,6 +258,7 @@ void InternalConnection::close() otherSide->disconnect(); otherSideWeak.reset(); + connectionActive = false; } VCMI_LIB_NAMESPACE_END diff --git a/lib/network/NetworkConnection.h b/lib/network/NetworkConnection.h index dc56ffc6a..fe6d34c2b 100644 --- a/lib/network/NetworkConnection.h +++ b/lib/network/NetworkConnection.h @@ -51,6 +51,7 @@ class InternalConnection final : public IInternalConnection, public std::enable_ std::weak_ptr otherSideWeak; std::shared_ptr io; INetworkConnectionListener & listener; + bool connectionActive = false; public: InternalConnection(INetworkConnectionListener & listener, const std::shared_ptr & context); diff --git a/lib/pathfinder/PathfinderUtil.h b/lib/pathfinder/PathfinderUtil.h index 3e1f4cbaf..10cfab9c3 100644 --- a/lib/pathfinder/PathfinderUtil.h +++ b/lib/pathfinder/PathfinderUtil.h @@ -40,15 +40,23 @@ namespace PathfinderUtil } else { + bool hasBlockedVisitable = false; + bool hasVisitable = false; + for(const CGObjectInstance * obj : tinfo.visitableObjects) { if(obj->isBlockedVisitable()) - return EPathAccessibility::BLOCKVIS; - else if(obj->passableFor(player)) - return EPathAccessibility::ACCESSIBLE; - else if(obj->ID != Obj::EVENT) - return EPathAccessibility::VISITABLE; + hasBlockedVisitable = true; + else if(!obj->passableFor(player) && obj->ID != Obj::EVENT) + hasVisitable = true; } + + if(hasBlockedVisitable) + return EPathAccessibility::BLOCKVIS; + if(hasVisitable) + return EPathAccessibility::VISITABLE; + + return EPathAccessibility::ACCESSIBLE; } } else if(tinfo.blocked()) diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index 823fe721c..527138de0 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -102,7 +102,8 @@ std::unique_ptr CConnection::retrievePack(const std::vector & if (packReader->position != data.size()) throw std::runtime_error("Failed to retrieve pack! Not all data has been read!"); - logNetwork->trace("Received CPack of type %s", typeid(result.get()).name()); + auto packRawPtr = result.get(); + logNetwork->trace("Received CPack of type %s", typeid(*packRawPtr).name()); deserializer->loadedPointers.clear(); deserializer->loadedSharedPointers.clear(); return result; diff --git a/server/processors/TurnOrderProcessor.cpp b/server/processors/TurnOrderProcessor.cpp index 899df2b04..36c3bffef 100644 --- a/server/processors/TurnOrderProcessor.cpp +++ b/server/processors/TurnOrderProcessor.cpp @@ -249,6 +249,8 @@ void TurnOrderProcessor::doStartNewDay() assert(awaitingPlayers.empty()); assert(actingPlayers.empty()); + gameHandler->onNewTurn(); + bool activePlayer = false; for (auto player : actedPlayers) { @@ -264,7 +266,6 @@ void TurnOrderProcessor::doStartNewDay() std::swap(actedPlayers, awaitingPlayers); - gameHandler->onNewTurn(); updateAndNotifyContactStatus(); tryStartTurnsForPlayers(); }